{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"},{"name":"Jendrik Brack","url":"https://daily-devops.net/authors/jendrik/"}],"description":"Recent content in C# Source Generators: Patterns \u0026 Build Costs on Daily DevOps \u0026 .NET","favicon":"https://daily-devops.net/images/logo_hu_6465d873dfa490cf.png","feed_url":"https://daily-devops.net/tags/sourcegenerators/feed.json","home_page_url":"https://daily-devops.net/tags/sourcegenerators/","icon":"https://daily-devops.net/images/logo_hu_5926de77762241ba.png","items":[{"authors":[{"name":"Martin Stühmer","url":"https://daily-devops.net/authors/martin/"}],"content_html":"\u003cp\u003eYou add a NuGet package. Build time jumps from 2 seconds to 8. You rebuild a second time: still 8 seconds. You change one line of code: 8 seconds again. The package description said nothing about this. You just quietly accepted a 300% tax on every build for the rest of the project\u0026rsquo;s life.\u003c/p\u003e\n\u003cp\u003eThat package ships a source generator.\u003c/p\u003e\n\u003cp\u003eSource generators are one of the most powerful additions to the .NET compiler platform. They are also one of the most invisible performance costs in modern .NET development. Everyone writes about what they can do. Nobody writes about what they cost.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"what-source-generators-actually-do-on-every-build\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#what-source-generators-actually-do-on-every-build\" title=\"What Source Generators Actually Do (On Every Build)\"\u003eWhat Source Generators Actually Do (On Every Build)\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe mental model most developers have: source generators run once, generate some code, done. That is wrong.\u003c/p\u003e\n\u003cp\u003eSource generators run as part of the Roslyn compilation pipeline. Every time you build (full build, incremental build, background build triggered by saving a file), every registered generator runs. Not optionally. Not conditionally. Every time.\u003c/p\u003e\n\u003cp\u003eA typical mid-sized .NET solution in 2025 has more active source generators than you think. Add them up:\u003c/p\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003ePackage\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eGenerator\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMicrosoft.Extensions.Logging\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eLoggerMessageGenerator\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eSystem.Text.Json\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eJsonSerializerSourceGenerator\u003c/code\u003e\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eAutoMapper.Extensions.Microsoft.DependencyInjection\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMapping generator\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMapperly\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMapper generator\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003ccode\u003eMicrosoft.NET.Sdk.Maui\u003c/code\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eMultiple generators\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eAny DI framework\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eRegistration generator\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eAny gRPC tooling\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eService/client generator\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eEight to twelve active generators per project is not unusual. Each one is a Roslyn plugin executing against your full syntax tree on every build.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-two-kinds-of-source-generators\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#the-two-kinds-of-source-generators\" title=\"The Two Kinds of Source Generators\"\u003eThe Two Kinds of Source Generators\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNot all source generators are created equal. This distinction matters enormously for build performance.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ccode\u003eISourceGenerator\u003c/code\u003e\u003c/strong\u003e: the original API from .NET 5. Receives the full compilation. Runs completely on every build. No caching, no incremental logic. If you have one of these, you pay full price every time regardless of what changed.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e\u003ccode\u003eIIncrementalGenerator\u003c/code\u003e\u003c/strong\u003e: introduced in .NET 6. Uses a pipeline model that tracks which inputs actually changed. If your code change does not affect the generator\u0026rsquo;s inputs, the generator produces cached output and skips real work. Used correctly, incremental generators approach zero cost on unchanged code.\u003c/p\u003e\n\u003cp\u003eThe catch: many popular NuGet packages still ship \u003ccode\u003eISourceGenerator\u003c/code\u003e implementations. The API is not deprecated. There is no warning when you install a non-incremental generator. You find out at build time.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"measuring-the-damage\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#measuring-the-damage\" title=\"Measuring the Damage\"\u003eMeasuring the Damage\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou cannot fix what you cannot measure. Fortunately, MSBuild gives you everything you need.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"binary-log\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#binary-log\" title=\"Binary Log\"\u003eBinary Log\u003c/a\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edotnet build -bl\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis produces \u003ccode\u003emsbuild.binlog\u003c/code\u003e in your project directory. Open it with \u003ca href=\"https://msbuildlog.com/\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003eMSBuild Structured Log Viewer\u003c/a\u003e. Search for \u003ccode\u003eGeneratorDriver\u003c/code\u003e or \u003ccode\u003eRunGenerators\u003c/code\u003e. You will see each generator, its execution time, and how often it ran.\u003c/p\u003e\n\u003cp\u003eA real example from a project I worked on:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-txt\" data-lang=\"txt\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRunGenerators (net9.0)\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ├── JsonSerializerSourceGenerator    42ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ├── LoggerMessageGenerator            8ms\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ├── MapperlyGenerator               890ms   ← problem\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  └── AutoMapperGenerator             340ms   ← problem\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThat is 1.2 seconds per build from two mapping generators. At 300 builds per day (realistic for an active developer with file-save triggers), that is 6 minutes of daily waste. Per developer. On a 5-person team: 30 minutes every day.\u003c/p\u003e\n\n\n\n\n\u003ch3 id=\"roslyn-generator-timing\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#roslyn-generator-timing\" title=\"Roslyn Generator Timing\"\u003eRoslyn Generator Timing\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eFor finer granularity, set the \u003ccode\u003eReportAnalyzer\u003c/code\u003e property:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;PropertyGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;ReportAnalyzer\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/ReportAnalyzer\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/PropertyGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eBuild output will include per-generator timing in milliseconds. Slower and less detailed than binlog, but useful for quick checks without installing additional tools.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"multi-targeting-multiplies-everything\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#multi-targeting-multiplies-everything\" title=\"Multi-Targeting Multiplies Everything\"\u003eMulti-Targeting Multiplies Everything\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere is the cost multiplier nobody mentions in the documentation:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;TargetFrameworks\u0026gt;\u003c/span\u003enet8.0;net9.0\u003cspan class=\"nt\"\u003e\u0026lt;/TargetFrameworks\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eEvery source generator runs once per target framework. Two targets: double the cost. Three targets: triple. That MapperlyGenerator eating 890ms? Now it costs 1,780ms on every build. Every registered generator, every target, every time.\u003c/p\u003e\n\u003cp\u003eLibrary authors supporting \u003ccode\u003enet6.0;net7.0;net8.0;net9.0\u003c/code\u003e and shipping a non-incremental generator are imposing a 4× multiplier on every consumer of their package.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"hot-reload-the-silent-incompatibility\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#hot-reload-the-silent-incompatibility\" title=\"Hot Reload: The Silent Incompatibility\"\u003eHot Reload: The Silent Incompatibility\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSource generators and \u003ca href=\"https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-watch\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e.NET Hot Reload\u003c/a\u003e have a complicated relationship.\u003c/p\u003e\n\u003cp\u003eHot Reload works by applying incremental changes to a running process without a full restart. Source generators complicate this because the generated code might need to change when your code changes, and the Hot Reload mechanism cannot always determine whether that is safe.\u003c/p\u003e\n\u003cp\u003eThe result: if your project has source generators that interact with the code you changed, Hot Reload silently falls back to a full rebuild and restart. The \u003ccode\u003edotnet watch\u003c/code\u003e output tells you this happened:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-txt\" data-lang=\"txt\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewarn: Hot reload of changes succeeded but some changes required application restart.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eYou lose the instant feedback loop that makes Hot Reload valuable. With several active generators, you may find Hot Reload effectively never works for your use case.\u003c/p\u003e\n\u003cp\u003eTo diagnose which generators break Hot Reload, temporarily remove them one by one and observe whether \u003ccode\u003edotnet watch\u003c/code\u003e starts applying changes without restart.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"ide-latency-the-intellisense-tax\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#ide-latency-the-intellisense-tax\" title=\"IDE Latency: The IntelliSense Tax\"\u003eIDE Latency: The IntelliSense Tax\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSource generators run in the background inside Visual Studio and Rider to keep generated code available for IntelliSense, navigation, and error highlighting. This is a continuous background process, not just a build-time concern.\u003c/p\u003e\n\u003cp\u003eNon-incremental generators re-run whenever the IDE detects a change to your syntax tree. Type a character, save a file, the generator chain kicks off. If your generators are slow, you experience this as:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIntelliSense suggestions appearing late or disappearing temporarily\u003c/li\u003e\n\u003cli\u003e\u0026ldquo;Analyzing\u0026hellip;\u0026rdquo; spinners that block navigation\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eGo to Definition\u003c/code\u003e jumping to stale generated code\u003c/li\u003e\n\u003cli\u003eIntermittent red squiggles on valid code\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis is hard to attribute directly because IDEs do not surface per-generator timings in their UI. The binlog approach does not help here either since that measures CLI builds. Your best signal is disabling generators one at a time via \u003ccode\u003e\u0026lt;Analyzer Remove=\u0026quot;...\u0026quot; /\u0026gt;\u003c/code\u003e and observing whether IDE responsiveness improves.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"when-source-generators-are-worth-it\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#when-source-generators-are-worth-it\" title=\"When Source Generators Are Worth It\"\u003eWhen Source Generators Are Worth It\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSource generators are not the problem. The problem is using them without understanding the trade-off.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eClearly worth it:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e[LoggerMessage]\u003c/code\u003e source generator: eliminates allocation on every log call, compiler-enforced message templates. The runtime savings at high throughput far outweigh the build cost.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eSystem.Text.Json\u003c/code\u003e source generator: AOT-compatible serialization, zero reflection at runtime. Required for Native AOT scenarios, significant throughput improvement in hot paths.\u003c/li\u003e\n\u003cli\u003eStrongly-typed ID generators (like \u003ca href=\"https://github.com/andrewlock/StronglyTypedId\" target=\"_blank\" rel=\"noopener external noreferrer\"\u003e\u003ccode\u003eStronglyTypedId\u003c/code\u003e\u003c/a\u003e): compile-time correctness guarantee, zero runtime cost.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003cstrong\u003eOften not worth it:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eMapping generators for simple DTOs where a hand-written mapper takes 20 lines and compiles instantly\u003c/li\u003e\n\u003cli\u003eDI registration generators that save writing \u003ccode\u003eservices.AddScoped\u0026lt;IFoo, Foo\u0026gt;()\u003c/code\u003e a few times\u003c/li\u003e\n\u003cli\u003eBoilerplate generators for code that changes rarely and where T4 templates or a one-time script would suffice\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe deciding question: does this generator eliminate runtime cost, enforce correctness at compile time, or enable something impossible without it? If the answer is \u0026ldquo;it saves me from writing some repetitive code,\u0026rdquo; a T4 template or a code snippet achieves the same result without touching every build.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"how-to-check-whether-a-generator-is-incremental\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#how-to-check-whether-a-generator-is-incremental\" title=\"How to Check Whether a Generator Is Incremental\"\u003eHow to Check Whether a Generator Is Incremental\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBefore adding a package that ships a source generator, check whether its generator implements \u003ccode\u003eIIncrementalGenerator\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThe fast way: look at the package\u0026rsquo;s GitHub repository and search for \u003ccode\u003eIIncrementalGenerator\u003c/code\u003e vs \u003ccode\u003eISourceGenerator\u003c/code\u003e. If the generator implements \u003ccode\u003eISourceGenerator\u003c/code\u003e, it is non-incremental.\u003c/p\u003e\n\u003cp\u003eThe programmatic way: inspect the assembly directly.\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-csharp\" data-lang=\"csharp\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eusing\u003c/span\u003e \u003cspan class=\"nn\"\u003eSystem.Reflection\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eassembly\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAssembly\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eLoadFrom\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;path/to/generator.dll\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003egeneratorTypes\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eassembly\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetTypes\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWhere\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003et\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003et\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetInterfaces\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAny\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFullName\u003c/span\u003e \u003cspan class=\"p\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Microsoft.CodeAnalysis.ISourceGenerator\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e               \u003cspan class=\"p\"\u003e||\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFullName\u003c/span\u003e \u003cspan class=\"p\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Microsoft.CodeAnalysis.IIncrementalGenerator\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e));\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eforeach\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003egeneratorTypes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kt\"\u003evar\u003c/span\u003e \u003cspan class=\"n\"\u003eisIncremental\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGetInterfaces\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eAny\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ei\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003ei\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eFullName\u003c/span\u003e \u003cspan class=\"p\"\u003e==\u003c/span\u003e \u003cspan class=\"s\"\u003e\u0026#34;Microsoft.CodeAnalysis.IIncrementalGenerator\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eConsole\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eWriteLine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s\"\u003e$\u0026#34;{type.Name}: {(isIncremental ? \u0026#34;\u003c/span\u003e\u003cspan class=\"n\"\u003eIncremental\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34; : \u0026#34;\u003c/span\u003e\u003cspan class=\"n\"\u003eNon\u003c/span\u003e\u003cspan class=\"p\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eincremental\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;)}\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eIf the generator is non-incremental and the package is popular, check whether there is an open issue or PR for the migration. Many maintainers have not made the switch simply because nobody asked.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"mitigation-strategies\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#mitigation-strategies\" title=\"Mitigation Strategies\"\u003eMitigation Strategies\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhen you cannot remove a generator but need to reduce its cost:\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eEmit and cache generated files:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;PropertyGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;EmitCompilerGeneratedFiles\u0026gt;\u003c/span\u003etrue\u003cspan class=\"nt\"\u003e\u0026lt;/EmitCompilerGeneratedFiles\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;CompilerGeneratedFilesOutputPath\u0026gt;\u003c/span\u003e$(BaseIntermediateOutputPath)Generated\u003cspan class=\"nt\"\u003e\u0026lt;/CompilerGeneratedFilesOutputPath\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/PropertyGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eThis writes generated files to disk. You can commit them to source control and exclude the generator from CI builds when inputs have not changed. Adds complexity; worth it for slow generators on large projects.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eIsolate generators to dedicated projects:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eSplit your solution so that the code triggering expensive generators lives in a dedicated project that changes rarely. The generator only runs when that project rebuilds, not on every change to your main project.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDisable generators in specific configurations:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-xml\" data-lang=\"xml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;ItemGroup\u003c/span\u003e \u003cspan class=\"na\"\u003eCondition=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#39;$(Configuration)\u0026#39; == \u0026#39;Debug\u0026#39;\u0026#34;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;Analyzer\u003c/span\u003e \u003cspan class=\"na\"\u003eRemove=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;@(Analyzer)\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003eCondition=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;\u0026#39;%(Filename)\u0026#39; == \u0026#39;SlowGenerator\u0026#39;\u0026#34;\u003c/span\u003e \u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/ItemGroup\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003eUse this sparingly. It can cause the Debug build to diverge from Release in ways that mask real errors.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eProfile before optimizing:\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eMeasure with binlog first. The generator you suspect is slow is often not the actual problem. The one you never thought about frequently is.\u003c/p\u003e\n\n\n\n\n\u003ch2 id=\"the-bigger-picture\"\u003e\u003ca href=\"/posts/dotnet-source-generators-hidden-costs/#the-bigger-picture\" title=\"The Bigger Picture\"\u003eThe Bigger Picture\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSource generators sit at the intersection of a real tension in modern .NET: zero runtime cost requires paying that cost somewhere else, and \u0026ldquo;somewhere else\u0026rdquo; is build time and IDE responsiveness.\u003c/p\u003e\n\u003cp\u003eThe .NET ecosystem has moved fast on source generators since .NET 5. The migration from \u003ccode\u003eISourceGenerator\u003c/code\u003e to \u003ccode\u003eIIncrementalGenerator\u003c/code\u003e is ongoing but incomplete. Many widely-used packages still ship non-incremental generators because the migration requires significant effort and the existing API works.\u003c/p\u003e\n\u003cp\u003eAs a consumer, the tools are available to you. Measure with binlog. Understand whether each generator pays its way. Push back on packages that impose non-incremental generators for convenience features.\u003c/p\u003e\n\u003cp\u003eThe build time you save is your own.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eProfile first. \u003cstrong\u003eThen optimize.\u003c/strong\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n","date_modified":"2026-05-26T10:22:03+02:00","date_published":"2026-05-07T17:00:00+02:00","id":"https://daily-devops.net/posts/dotnet-source-generators-hidden-costs/","language":"en","summary":"You added a NuGet package and your build jumped from 2 to 8 seconds. That package ships a source generator. Here is what it costs and how to find out.","tags":["sourcegenerators","dotnet","performance","csharp","bestpractices","softwareengineering"],"title":"Source Generators: The Build Performance Killer","url":"https://daily-devops.net/posts/dotnet-source-generators-hidden-costs/"}],"language":"en","title":"C# Source Generators: Patterns \u0026 Build Costs on Daily DevOps \u0026 .NET","version":"https://jsonfeed.org/version/1.1"}