Newsletter Archive
Browse through our collection of past newsletters. Each edition is packed with C# and .NET insights.
Page 1 of 12 (36 editions)
[GeneratedRegex]: Your Regex, but Make It Fast
Regex in .NET has always been powerful, but let’s be honest, every time you write new Regex(pattern) you feel a tiny pang of guilt about the runtime compilation cost. And if you’re using Regex.IsMatch() with a string literal in a loop? That’s a performance review waiting to happen. [GeneratedRegex] fixes all of this by source-generating your regex at compile time.
The syntax is minimal. Slap the attribute on a partial method that returns Regex, and the compiler does the rest:
using System.Text.RegularExpressions;
foreach (var input in inputs){ Console.WriteLine(EmailRegex().IsMatch(input) ? $"✅ {input}" : $"❌ {input}");}
partial class Program{ [GeneratedRegex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$", RegexOptions.IgnoreCase)] private static partial Regex EmailRegex();}What happens behind the scenes is pretty slick. The source generator analyzes your pattern and emits a custom Regex-derived class with a hand-optimized TryFindNextPossibleStartingPosition and TryMatchAtCurrentPosition. No interpretation, no runtime compilation. Just raw, ahead-of-time matching logic. This means it’s also fully Native AOT compatible, which new Regex() is not.
You also get free IDE diagnostics. If your pattern has a syntax error, you’ll see it as a compile-time warning, not a runtime ArgumentException thrown at 2 AM in production. The Roslyn analyzer even catches common pitfalls like catastrophic backtracking patterns.
Performance-wise, benchmarks consistently show [GeneratedRegex] matching 2-5x faster than interpreted regex, with zero allocation overhead on the matching path. If you have any static readonly Regex fields in your codebase, migrating them is a five-second refactor with a massive payoff. Your patterns deserve the VIP treatment.
Don't miss the next tip 💧
Get a .NET tip and curated links delivered to your inbox every week.
PersistentComponentState: Surviving Blazor’s Prerender-to-Interactive Handoff
Blazor Server and Blazor Auto both support prerendering: the server renders your page to HTML on the first request so the user sees content immediately. Then the interactive runtime boots, components re-initialize, and OnInitializedAsync runs again. Your API calls fire a second time for data you already had moments ago.
The result? A flash of empty content, wasted resources, and double the cost for metered APIs.
The Problem
@code { private Product[]? products;
protected override async Task OnInitializedAsync() { // Runs TWICE: once during prerender, once when interactive products = await Http.GetFromJsonAsync<Product[]>("/api/products"); }}During prerender, the data loads and renders into HTML. Then the interactive runtime starts, products resets to null, “Loading…” flashes, and the fetch runs again.
The Fix (Manual Approach)
Inject PersistentComponentState to stash data during prerender and restore it on interactive boot:
@inject PersistentComponentState ApplicationState@implements IDisposable
@code { private Product[]? products; private PersistingComponentStateSubscription _subscription;
protected override async Task OnInitializedAsync() { _subscription = ApplicationState.RegisterOnPersisting(PersistData);
if (!ApplicationState.TryTakeFromJson<Product[]>( "products", out var restored)) restored = await Http.GetFromJsonAsync<Product[]>("/api/products");
products = restored; }
private Task PersistData() { ApplicationState.PersistAsJson("products", products); return Task.CompletedTask; }
public void Dispose() => _subscription.Dispose();}On the prerender pass, TryTakeFromJson returns false, so the fetch runs. Blazor serializes the data into a hidden element in the HTML. On the interactive boot, TryTakeFromJson returns true with the data. No second fetch.
The Fix (.NET 10): The [PersistentState] Attribute
.NET 10 simplifies this to a one-liner:
@code { [PersistentState] public Product[]? Products { get; set; }
protected override async Task OnInitializedAsync() { Products ??= await Http.GetFromJsonAsync<Product[]>("/api/products"); }}The [PersistentState] attribute handles serialization, persistence, and restoration automatically. The ??= check ensures the API call only runs when the data wasn’t already restored.
Gotchas
- Keep it small. The state is serialized as JSON and embedded in the HTML. A few records are fine. Thousands of rows are not.
- Keys must be unique. The property name (or string key in the manual approach) must be unique across all components on the page.
- Not a general cache. It only transfers state from the prerender pass to the interactive boot. It doesn’t persist across navigations or page reloads.
Key Takeaway
PersistentComponentState eliminates the double-fetch problem in Blazor prerendering. In .NET 10, [PersistentState] makes it a one-liner. If your components flash “Loading…” after the page already rendered with data, this is the fix.
Don't miss the next tip 💧
Get a .NET tip and curated links delivered to your inbox every week.
PeriodicTimer: The Async-Friendly Way to Schedule Recurring Work
If you’ve written a BackgroundService with while + Task.Delay, you’ve probably introduced drift without realizing it.
The Problem
while (!stoppingToken.IsCancellationRequested){ await DoWorkAsync(); // takes 30 seconds await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // then waits 5 more}// Actual interval: 5:30, not 5:00. Drifts further over time.The delay starts after the work finishes, so your interval is always work time + delay time.
The Fix
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(stoppingToken)){ await DoWorkAsync();}PeriodicTimer ticks at a fixed interval regardless of how long the work takes. If the work runs past a tick, the next WaitForNextTickAsync returns immediately (one tick is buffered), then resumes the normal cadence. No drift, no overlap, no queued-up flood of missed ticks.
In a BackgroundService
public class PricePollingService( IHttpClientFactory httpFactory, ILogger<PricePollingService> logger) : BackgroundService{ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var timer = new PeriodicTimer(TimeSpan.FromSeconds(60));
while (await timer.WaitForNextTickAsync(stoppingToken)) { try { var client = httpFactory.CreateClient("pricing"); var price = await client.GetFromJsonAsync<decimal>("/api/price", stoppingToken); logger.LogInformation("Current price: {Price:C}", price); } catch (HttpRequestException ex) { logger.LogWarning(ex, "Fetch failed. Will retry next tick."); } } }}Disposing the timer causes WaitForNextTickAsync to return false, so the loop exits cleanly. Cancellation via stoppingToken works the same way.
Task.Delay vs PeriodicTimer
| Behavior | Task.Delay loop | PeriodicTimer |
|---|---|---|
| Interval includes work time? | Yes (drift) | No (fixed) |
| Overlap possible? | Yes, if not careful | No |
| Missed ticks pile up? | N/A | No, one buffered |
| Disposal exits the loop? | No | Yes |
Key Takeaway
PeriodicTimer is a one-line swap that gives your background services fixed-interval ticks, no drift, and clean shutdown. If you have a while + Task.Delay loop, replace it.
Don't miss the next tip 💧
Get a .NET tip and curated links delivered to your inbox every week.