C#
.NET
Backend-Development
App-Development

.NET Source Generators

A two-day workshop on Roslyn-based compile-time code generation: build incremental source generators and analyzers, write generator unit tests, read external data at compile time, and implement C# interceptors — eliminating boilerplate and improving application performance.

.NET Source Generators

Duration: 2 days

Target Audience

.NET developers who want to leverage compile-time code generation to eliminate boilerplate, enforce patterns, and improve application performance. The workshop suits developers building frameworks, libraries, or large-scale applications where repetitive code patterns are a maintenance burden.

Prerequisites

  • Solid experience with C# and .NET (C# 10+ recommended)
  • Familiarity with the Roslyn compiler API is helpful but not required
  • Visual Studio 2022/2026 or Rider with .NET 10 SDK installed

Learning Objectives

By the end of this workshop, participants will be able to:

  • Understand what source generators are, how they integrate with the Roslyn compilation pipeline, and when to use them over alternatives such as reflection or T4 templates
  • Consume existing source generators from NuGet packages and configure them in a project
  • Create incremental source generators from scratch using the IIncrementalGenerator API
  • Write unit tests for source generators with the Microsoft.CodeAnalysis.Testing libraries
  • Read external data (XML, JSON) at compile time to drive code generation
  • Implement C# interceptors to replace method calls at compile time without modifying existing source
  • Apply performance best practices: incremental pipelines, caching, diagnostic reporting, and avoiding unnecessary regeneration

Course Outline

Day 1 – Morning: Understanding and Using Source Generators

1. The Roslyn Compilation Pipeline

  • How the C# compiler works: syntax trees, semantic models, and symbols
  • Where source generators fit in the compilation pipeline
  • Source generators vs. reflection, T4 templates, and Fody/IL weaving: trade-offs and use cases
  • Anatomy of a generated file: what gets emitted, when, and how it is visible to the rest of the project

2. Consuming Source Generators

  • Adding generator packages from NuGet (e.g., System.Text.Json, Microsoft.Extensions.Logging, AutoMapper, Refit)
  • Analyzer and generator output in the IDE: viewing generated files in Visual Studio and Rider
  • <EmitCompilerGeneratedFiles> and <CompilerGeneratedFilesOutputPath>: persisting generated output for inspection
  • Configuring generators via [assembly: ...] attributes and AdditionalFiles
  • Debugging generated code: breakpoints in generated files, disabling specific generators

3. Writing Your First Incremental Source Generator

  • Project setup: netstandard2.0 target, Microsoft.CodeAnalysis.CSharp NuGet reference, IsRoslynComponent property
  • Implementing IIncrementalGenerator and registering with [Generator]
  • The IncrementalGeneratorInitializationContext: SyntaxProvider, CompilationProvider, AdditionalTextsProvider
  • Filtering syntax nodes with CreateSyntaxProvider and predicate/transform pairs
  • Emitting source with context.RegisterSourceOutput
  • Generating a simple ToString-style helper from class attributes

Lab: Create a generator that reads classes annotated with [GenerateDto] and emits a corresponding DTO record with mapped properties.

Day 1 – Afternoon: Testing Source Generators

4. Unit Testing Strategy

  • Why source generator testing differs from regular unit testing: no runtime, compiler as the test host
  • The Microsoft.CodeAnalysis.CSharp.Testing and Microsoft.CodeAnalysis.Testing NuGet packages
  • CSharpSourceGeneratorTest<TGenerator, TVerifier>: anatomy of a generator test
  • Providing input source, specifying expected generated output, and asserting diagnostics
  • Testing that no output is emitted for inputs that do not match
  • Snapshot testing with Verify.SourceGenerators as an alternative approach

5. Testing Diagnostics and Error Cases

  • Reporting diagnostics from a generator: DiagnosticDescriptor, severity levels, locations
  • Testing that the generator emits the correct warning or error for invalid input
  • Testing incremental behavior: verifying that unchanged inputs do not trigger regeneration
  • Integration testing: building a sample consumer project and asserting compilation succeeds

6. Test-Driven Generator Development

  • Writing tests before implementing generator logic
  • Iterating on generator output by updating expected snapshots
  • Continuous integration setup: running generator tests in a CI pipeline without special tooling

Lab: Write a full test suite for the Day 1 Morning generator: happy path, missing attribute, invalid type, and diagnostic emission tests.

Day 2 – Morning: Advanced Code Generation

7. Generating Code from External Files (XML and JSON)

  • AdditionalFiles in MSBuild: adding .xml and .json files as generator inputs
  • AdditionalTextsProvider: accessing file content and paths at compile time
  • Parsing XML with System.Xml.Linq inside the generator
  • Parsing JSON with System.Text.Json (source-generation–safe subset) or Newtonsoft.Json
  • Combining AdditionalTextsProvider with CompilationProvider for type-aware generation
  • Caching external file reads: WithTrackingName, Collect, and avoiding re-parse on unrelated changes
  • Generating strongly typed configuration classes, API client stubs, or enum definitions from schema files

8. Reading Attributes and Semantic Model Data

  • Walking the semantic model: INamedTypeSymbol, IMethodSymbol, IPropertySymbol
  • Extracting attribute constructor arguments and named parameters
  • Handling partial classes and the partial keyword requirement
  • Generating extension methods, interfaces, and nested types from semantic data
  • Handling generics, nullable reference types, and accessibility modifiers correctly

Lab: Build a generator that reads an api-schema.xml AdditionalFile and emits a strongly typed C# client class with one method per endpoint defined in the schema, including XML documentation comments.

Day 2 – Afternoon: Interceptors and Performance Optimization

9. C# Interceptors

  • What interceptors are: a compiler feature that redirects a specific call site to a different method at compile time
  • Enabling interceptors: <Features>interceptors</Features> in the project file; the [InterceptsLocation] attribute
  • Generating [InterceptsLocation] from a source generator: capturing file path, line, and character from SyntaxReference
  • Use cases: replacing logging calls with zero-allocation structured logging, replacing reflection-based serialization with generated code, inlining configuration reads
  • Limitations and caveats: one interceptor per call site, no interception of virtual dispatch, stability across edits
  • Combining interceptors with incremental generators for a fully automated optimization pipeline

Lab: Write a generator that intercepts all calls to a [SlowPath]-annotated method and redirects them to a generated fast-path implementation that avoids reflection.

10. Optimizing Source Generators for Build Performance

  • The incremental generator model: why IIncrementalGenerator replaced ISourceGenerator
  • Structuring the pipeline for maximum caching: immutable value objects, IEquatable<T> on pipeline inputs, EquatableArray<T> wrappers
  • WithTrackingName for build performance diagnostics with Roslyn's GeneratorDriverRunResult
  • Avoiding expensive work in the transform step: move semantic lookups to a separate pipeline stage
  • Profiling generator execution time: BenchmarkDotNet, build binary logs (-bl), and MSBuild structured log viewer
  • Common mistakes: capturing Compilation directly, using mutable state, missing IEquatable<T> on model types
  • Parallel and multi-output generators: RegisterImplementationSourceOutput vs. RegisterSourceOutput

11. Diagnostics, Versioning, and Packaging

  • Shipping a generator as a NuGet package: buildTransitive assets, analyzers/dotnet/cs folder layout
  • Versioning generators independently of the library they augment
  • Suppressing specific generator warnings per project
  • Multi-targeting: shipping the generator on netstandard2.0 while the runtime library targets net10.0
  • Debugging a packaged generator: IsRoslynComponent, source link, and symbol packages

Lab: Refactor the Day 1 generator into a NuGet-packageable structure, add build performance tracking, verify incremental caching with a multi-project solution, and measure regeneration cost with and without IEquatable<T> on the pipeline model.

Hands-on Labs

  • Consume and inspect the output of System.Text.Json and Microsoft.Extensions.Logging source generators in an existing project.
  • Create a [GenerateDto] incremental generator that emits DTO records from annotated domain classes.
  • Write a complete unit test suite using Microsoft.CodeAnalysis.CSharp.Testing, covering happy path, diagnostics, and no-output cases.
  • Build a generator that parses an api-schema.xml AdditionalFile and emits a strongly typed HTTP client class with XML doc comments.
  • Implement a C# interceptor generator that replaces reflection-based method calls with generated, zero-allocation equivalents.
  • Profile and optimize an incremental generator pipeline: add IEquatable<T> to model types, verify caching behavior, and measure build time improvement.
  • Package the completed generator as a NuGet package with correct buildTransitive layout and test it from a separate consumer project.

Outcomes

  • Able to evaluate when source generators are the right tool and articulate the trade-offs against reflection, T4, and IL weaving
  • Proficient in implementing IIncrementalGenerator with correctly structured, cache-friendly pipelines
  • Skilled in testing generators with Microsoft.CodeAnalysis.CSharp.Testing including diagnostic and no-output assertions
  • Capable of driving code generation from external XML and JSON schema files via AdditionalFiles
  • Experienced in writing C# interceptors to transparently replace call sites at compile time
  • Able to optimize generator pipelines for build performance using IEquatable<T>, WithTrackingName, and staged transforms
  • Ready to ship generators as NuGet packages with correct multi-targeting and asset layout

Advanced Learning Paths

  • Roslyn analyzers and code fixes: pairing generators with diagnostics that guide developers toward generated APIs
  • Scriban and T4-style templates inside generators for complex multi-file output
  • Integration with Verify.SourceGenerators for golden-file snapshot testing
  • Using source generators in Blazor and MAUI for compile-time route generation and platform-specific code
An unhandled error has occurred. Reload 🗙