Mastering C# Source Generators: Write Less Code, Do More
As software developers, we're constantly walking a tightrope between delivering features quickly and maintaining clean, maintainable codebases. How often have you found yourself copy-pasting code, writing tedious boilerplate, or wishing there was a smarter way to generate repetitive code? Enter C# Source Generators: a powerful tool that lets you write less code while achieving more.
In this blog post, we’ll explore how C# Source Generators can automate boilerplate code generation, reduce potential bugs, and improve overall development efficiency. Whether you’re curious about what Source Generators are, how they work, or how to implement them in real-world scenarios, this guide has you covered.
What Are C# Source Generators?
In simple terms, Source Generators are a feature introduced in C# 9.0 (part of .NET 5) that allows you to programmatically generate C# code during build time. Unlike runtime code generation (e.g., reflection or IL weaving), Source Generators operate during the compilation phase, injecting additional code into your project before it is compiled into an executable or library.
Think of Source Generators as a way to “teach” the compiler to write code for you. If you’ve ever used tools like Entity Framework Core, JSON serializers, or dependency injection frameworks, you’ve likely benefited from generated code. Source Generators empower you to create similar tools that tailor code generation to your specific needs.
Why Use Source Generators?
- Eliminate Boilerplate Code: Say goodbye to repetitive patterns.
- Increase Maintainability: Generated code is consistent and adheres to a single source of truth.
- Boost Performance: Generated code is available at compile time, avoiding runtime overhead.
- Reduce Errors: Automating repetitive tasks minimizes human error.
How Do Source Generators Work?
At a high level, Source Generators hook into the Roslyn compiler pipeline and provide new code files to include in your project. These files are seamlessly integrated into the compilation process. Here’s a simplified breakdown of the process:
- Input: The Source Generator analyzes your existing code (syntax trees, semantic models, etc.).
- Code Generation: Based on this analysis, it programmatically generates new C# code.
- Output: The generated code is added to your project as if you wrote it manually.
Here’s a mental analogy: Imagine you’re a chef preparing a recipe, but instead of chopping vegetables manually, you have a sous-chef who automatically preps everything for you based on your instructions. That sous-chef is your Source Generator.
Getting Started with Source Generators
To create a Source Generator, you’ll need to:
- Create a new class library project using the
.NET SDK
. - Reference the
Microsoft.CodeAnalysis
NuGet package. - Implement the
ISourceGenerator
interface.
Example: A Simple Hello World Generator
Let’s write a simple Source Generator that generates a class with a HelloWorld
method.
- Create a new project:
dotnet new classlib -n HelloWorldGenerator
cd HelloWorldGenerator
dotnet add package Microsoft.CodeAnalysis.CSharp
-
Implement the Generator:
Create a
HelloWorldGenerator.cs
file and implement theISourceGenerator
interface:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this example
}
public void Execute(GeneratorExecutionContext context)
{
// Define the source code to generate
const string source = @"
namespace GeneratedCode
{
public static class HelloWorld
{
public static string SayHello() => ""Hello, World!"";
}
}";
// Add the generated source code to the compilation
context.AddSource("HelloWorld.g.cs", SourceText.From(source, Encoding.UTF8));
}
}
-
Use the Generator:
- Reference this generator in another project.
- Call the generated code:
using System;
using GeneratedCode;
class Program
{
static void Main()
{
Console.WriteLine(HelloWorld.SayHello());
}
}
Output:
Hello, World!
Practical Use Cases of Source Generators
1. INotifyPropertyChanged Implementation
Implementing INotifyPropertyChanged
for every property in a view model can be tedious. A Source Generator can automatically create the necessary boilerplate code.
Example
Instead of writing this manually:
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string _name;
public string Name
{
get => _name;
set
{
_name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
You can annotate your classes and let a generator handle it:
[AutoNotify]
public partial class Person
{
private string _name;
}
The Source Generator would produce the required INotifyPropertyChanged
implementation behind the scenes.
2. Strongly-Typed ID Generation
Many applications use string-based identifiers (e.g., GUIDs) for entities. Using strongly-typed IDs improves type safety but can be verbose. A Source Generator can create these types for you.
Example
Define your entity like this:
[StronglyTypedId]
public partial struct OrderId { }
The generator creates a struct
with equality, comparison, and serialization logic automatically.
3. Serialization Helpers
Source Generators can create optimized serialization code for JSON, XML, or custom formats, reducing runtime overhead and improving performance.
Common Pitfalls (and How to Avoid Them)
- Overusing Generators: Not every repetitive task warrants a Source Generator. Use them for cases where manual code would be error-prone or time-consuming.
-
Debugging Complexity: It can be tricky to debug issues in generated code. Use the
context.ReportDiagnostic
method to log useful messages during generation. - Performance: Generators run during compilation, so poorly designed generators can slow down your build process. Always test performance with large solutions.
- Tooling Compatibility: Ensure your IDE and build tools support Source Generators. For example, older versions of Visual Studio might not show generated code correctly.
Key Takeaways and Next Steps
C# Source Generators are a game-changer for developers looking to automate boilerplate code generation and improve productivity. By integrating Source Generators into your workflow, you can:
- Write less repetitive code.
- Reduce bugs by relying on automated, consistent code generation.
- Focus on building features instead of wrestling with boilerplate.
Next Steps
- Experiment with writing simple Source Generators like the ones shown above.
- Explore real-world examples like
AutoNotify
from the CommunityToolkit.Mvvm package. - Dive into the
Microsoft.CodeAnalysis
API to understand how to analyze syntax trees and semantic models.
Happy coding, and may your codebases be ever cleaner and more maintainable! 🚀
Top comments (1)
Nice posting! I'd like to talk to you