Performance Optimization for .NET

Most .NET performance work goes wrong in the same place: optimising things that were never the bottleneck. Database round trips, N+1 LINQ queries, badly tuned thread pools, and synchronous I/O on async paths cost orders of magnitude more than the micro-optimisations developers reach for first. Performance writing here starts from that premise — measure with BenchmarkDotNet or PerfView, then decide whether the change is worth the readability tax.

That said, modern .NET has shipped a quiet stream of low-level primitives that genuinely move the needle when used in the right place. SearchValues<T> collapses linear scans for character or substring sets into vectorised lookups, and the analyzer rule CA1870 will tell you exactly where to apply it. The throughput difference on hot string-validation paths is not marginal — five-times speedups are routine, and the change is a couple of lines.

CompositeFormat removes the cost of re-parsing the same format string on every call, which matters in logging, response formatting, and template-heavy code. [ConstantExpected] is the API-design counterpart: it lets you mark a parameter as required-constant so callers and analyzers cooperate on letting the JIT specialise. Source generators replace reflection-based serialization or DI registration with code the compiler can inline, but they also tax build times in ways that are easy to overlook until your CI minutes triple.

Allocation patterns deserve their own attention. Span<T>, stack allocation, pooled buffers via ArrayPool<T>, and the LoggerMessage source generator on filtered debug paths all exist to keep the garbage collector out of the hot loop. None of them are free, and none of them help if the real cost is a missing index or a chatty HTTP client. The collection here is deliberately biased toward decisions that survive contact with profiling data rather than coding-style preferences with performance branding.

Incremental Source Generators Done Right: Ship It Without Breaking Consumers

Packaging Generators

Parts 1 through 3 of this series made the generator correct, incremental, and proven by tests. None of it reaches a single consumer if the NuGet package is laid out wrong — and the failure modes are silent: a DLL in lib/ instead of analyzers/dotnet/cs simply never loads, a missing dependency throws only inside the compiler, and a marker attribute delivered carelessly breaks InternalsVisibleTo in ways users cannot diagnose. This final part covers the packaging contract: the netstandard2.0 rule, the package layout, dependency bundling, attribute delivery, debugging the shipped bits, and the analyzer hygiene rules that keep you honest.
Incremental Source Generators Done Right: Prove the Cache Hits

Prove the Cache Hits

You followed the equality rules, you shaped the pipeline correctly, and your generator still might be re-running on every keystroke — because nobody ever asked the driver. Roslyn records exactly why each pipeline step re-executed, and you can assert against those reasons in a plain xUnit test. This is the test that turns “my generator is incremental” from a claim into a regression-protected fact, plus the catalog of anti-patterns it catches in the wild.
Incremental Source Generators Done Right: Pipeline Patterns That Scale

Pipeline Patterns

Part 1 of this series established the equality contract: every value flowing through an incremental generator pipeline must be comparable by value, or the cache misses and the generator re-runs on every keystroke. This part is about the pipeline itself — ForAttributeWithMetadataName as the entry point, where to filter, where to transform, how to combine providers without accidentally subscribing to the entire Compilation, and the Collect trap that silently breaks everything you fixed in part 1. It ends with a complete worked generator you can steal.
Incremental Source Generators Done Right: The Equality Contract

The Equality Contract

IIncrementalGenerator exists for one reason: to run only when its inputs actually change. Most generators fail at exactly that, because somewhere in the pipeline a value is compared by reference and the cache silently misses. This is part 1 of a 4-part series on getting incrementalism right, and it starts where every broken generator starts: the equality contract, the ImmutableArray trap, and the EquatableArray that fixes it.
.NET 11: The STS Release With C# 15 Union Types and Runtime-Async

.NET 11: The STS Release With C# 15 Union Types and Runtime-Async

STS releases are supposed to be quiet bridges to the next LTS. .NET 11 is not. The union keyword landed in Preview 2, runtime-async is now the default for the BCL and ASP.NET Core shared framework, System.Diagnostics.Process ships a major overhaul, and EF Core gains approximate vector search against SQL Server 2025. All facts sourced from the .NET repositories.