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.

.NET 11 Performance Gains That Matter for AI-Adjacent Backends

.NET 11 Performance Gains That Matter for AI-Adjacent Backends

Cancellation is the steady state for inference gateways, not the edge case, and .NET 11 targets exactly that pain: runtime async collapses per-boundary exception rethrows, JIT deabstraction cuts allocations, and bounds-check elimination speeds up tokenization loops. None of it makes a slow model call faster, but it buys flatter tail latency and fewer instances for the same throughput.
What Actually Changed Inside the .NET 11 JIT Compiler

What Actually Changed Inside the .NET 11 JIT Compiler

.NET 11’s JIT ships real codegen wins: guarded devirtualization, wider escape analysis, a smaller delegate layout, and sharper bounds-check elimination. All of them are conditional on shapes the JIT can prove, not blanket speedups; here is what changed, why, and how to check whether your hot path qualifies.
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.