C# Source Generators: Patterns & Build Costs

C# Source Generators are a Roslyn compiler feature introduced in .NET 5 that produce code at build time and inject it directly into the compilation pipeline. They eliminate runtime reflection, replace hand-written boilerplate with generated equivalents, and unlock metaprogramming patterns that previously required IL weaving or T4 templates. The cost — and most getting-started guides skip this part — is that every generator runs on every build, on every target framework, and inside the IDE while you type.

Add a single NuGet package that ships a source generator and a 2-second build can quietly become an 8-second build. Multiply that by every team member, every CI run, every Hot Reload cycle, and the tax accumulates fast. The package description rarely mentions it. The damage is real but not advertised.

The articles in this collection cover what source generators actually do under the hood. The incremental generator API exists specifically to reduce repeated work between builds, but only generators written carefully against the IIncrementalGenerator interface benefit from it — many published packages still use the older ISourceGenerator model that rebuilds everything on every keystroke. Measuring the cost requires binary logs and the right MSBuild diagnostic flags rather than guesswork.

Pragmatic patterns get specific attention. When generators are worth the cost — eliminating reflection in JSON serialization, building strongly typed configuration accessors, generating regex code that JITs faster than Regex.Compiled — the trade-off makes sense. When they are not — generating one-off helpers that could be a static method, replacing five lines of obvious code with a complex generator pipeline — the tax is pure overhead.

The collection also addresses the IntelliSense and Hot Reload regressions that compound across solutions with several generators stacked together, and the diagnostic techniques required to identify which generator is the actual culprit.

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.
TUnit.Mocks: No Castle, No Reflection, No Drama

TUnit.Mocks: No Castle, No Reflection, No Drama

Most .NET test projects rely on NSubstitute and Castle.DynamicProxy. That foundation is cracking: NativeAOT breaks it, trimming strips it, cold-start costs accumulate. TUnit.Mocks takes a different route: a source generator emits typed mocks at compile time, IService.Mock() (or Mock.Of<T>()) is the entry point, runtime reflection is gone.