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
IIncrementalGeneratorAPI - Write unit tests for source generators with the
Microsoft.CodeAnalysis.Testinglibraries - 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 andAdditionalFiles - Debugging generated code: breakpoints in generated files, disabling specific generators
3. Writing Your First Incremental Source Generator
- Project setup:
netstandard2.0target,Microsoft.CodeAnalysis.CSharpNuGet reference,IsRoslynComponentproperty - Implementing
IIncrementalGeneratorand registering with[Generator] - The
IncrementalGeneratorInitializationContext:SyntaxProvider,CompilationProvider,AdditionalTextsProvider - Filtering syntax nodes with
CreateSyntaxProviderand 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.TestingandMicrosoft.CodeAnalysis.TestingNuGet 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.SourceGeneratorsas 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)
AdditionalFilesin MSBuild: adding.xmland.jsonfiles as generator inputsAdditionalTextsProvider: accessing file content and paths at compile time- Parsing XML with
System.Xml.Linqinside the generator - Parsing JSON with
System.Text.Json(source-generation–safe subset) orNewtonsoft.Json - Combining
AdditionalTextsProviderwithCompilationProviderfor 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
partialkeyword 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 fromSyntaxReference - 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
IIncrementalGeneratorreplacedISourceGenerator - Structuring the pipeline for maximum caching: immutable value objects,
IEquatable<T>on pipeline inputs,EquatableArray<T>wrappers WithTrackingNamefor build performance diagnostics with Roslyn'sGeneratorDriverRunResult- 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
Compilationdirectly, using mutable state, missingIEquatable<T>on model types - Parallel and multi-output generators:
RegisterImplementationSourceOutputvs.RegisterSourceOutput
11. Diagnostics, Versioning, and Packaging
- Shipping a generator as a NuGet package:
buildTransitiveassets,analyzers/dotnet/csfolder layout - Versioning generators independently of the library they augment
- Suppressing specific generator warnings per project
- Multi-targeting: shipping the generator on
netstandard2.0while the runtime library targetsnet10.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.JsonandMicrosoft.Extensions.Loggingsource 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.xmlAdditionalFile 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
buildTransitivelayout 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
IIncrementalGeneratorwith correctly structured, cache-friendly pipelines - Skilled in testing generators with
Microsoft.CodeAnalysis.CSharp.Testingincluding 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
ScribanandT4-style templates inside generators for complex multi-file output- Integration with
Verify.SourceGeneratorsfor golden-file snapshot testing - Using source generators in Blazor and MAUI for compile-time route generation and platform-specific code