Sign in
Start generating code to save development time.
Source code generators automate the writing of repetitive code by integrating directly into the application's build process. This saves development time, enforces code consistency, and can improve runtime performance by avoiding reflection.
Writing boilerplate code like property setters and data transfer objects is a common and tedious part of software development. Source code generators offer a practical solution by automating these repetitive tasks. These tools analyze your existing code during the build phase and produce supplementary source files automatically.
This means less manual effort and more time to focus on important business logic. This article explains how these generators work, from low-level framework tools that create specific helpers to high-level builders that can generate entire applications. We will look at several options for web and mobile development.
Technically, source code generators are not a feature of the compiler itself, but rather tools that hook into the build process, of which the compiler is a central part. Here is a more technically precise version:
"Writing the same property setters or boilerplate code repeatedly is tedious in software development. A developer’s time is better spent on business logic than repetitive patterns. Source code generators offer a way to automate this work by integrating into the build process.
These tools analyze existing code and automatically generate supplementary source files before the final compilation. By creating this code at compile time, generators can improve an application's runtime performance by eliminating the need for runtime reflection or other dynamic processes. This approach also reduces manual coding and the potential for human error."
The process of source code generation follows a clear sequence. The compiler begins by analyzing a project's source files and building syntax trees to represent the code's structure. A source generator then inspects the resulting syntax information and produces new code based on its findings. Generating code is a key step, as the generator creates new source files automatically during compilation. These newly created files are then included with the original ones in the final compilation.
Here is a simple representation of the workflow:
The generator code executes during compilation, not at runtime. This method of compile-time metaprogramming provides its benefits without affecting the application’s final performance.
A basic generator implementation might look like this:
1[Generator] 2public class PropertyGenerator : ISourceGenerator 3{ 4 public void Execute(GeneratorExecutionContext context) 5 { 6 // Null check to ensure context is not null before using it 7 if (context == null) return; 8 9 // The following code block defines the generated class 10 var source = @" 11 public partial class GeneratedClass 12 { 13 public string AutoProperty { get; set; } 14 }"; 15 16 context.AddSource("GeneratedClass.g.cs", source); 17 } 18 19 public void Initialize(GeneratorInitializationContext context) 20 { 21 // Setup for the generator 22 } 23}
In this example, the Execute method is a method call invoked by the compiler, which then adds the generated source string to the project’s compilation using a code block to define the generated class.
Reduced Manual Work: It automates the creation of code for common patterns and can update or extend the current code without manual intervention.
Consistent Code: Maintains a uniform structure across different projects and modules, ensuring users benefit from more maintainable and consistent generator code.
No Runtime Impact: The generation happens simultaneously, so the runtime performance is unaffected.
Build Process Integration: Works with existing build tasks and continuous integration systems.
Support for Complex Scenarios: Can handle tasks like mapping database schemas or generating API clients.
Various tools are available for different platforms and use cases, from low-level framework-specific generators to high-level application builders. Many of these code generation tools are distributed as libraries and can be added to your project as a NuGet package. These tools often work closely with compilers to analyze and generate code, enabling advanced automation and integration within your development workflow.
These tools focus on generating complete or near-complete applications from high-level inputs like text descriptions or design files. They handle much of the architecture and setup, allowing for fast initial development cycles.
Rocket.new
Rocket is a platform designed for rapid application development. A user can input an idea, and the tool helps ship the first version of a website or application quickly. Its features include:
Design to Code: Converts Figma designs into code.
Multi-Platform Support: Generates code for Flutter (with state management), React, Next.js, and HTML with TailwindCSS.
Integrations: Connects with third-party services like GitHub, OpenAI, Anthropic, Gemini, Google Analytics, Google AdSense, and Perplexity.
Backend Services: Offers email provider integration via Resend, payment processing through Stripe, and database support with Supabase.
Deployment: Supports shipping applications via Netlify.
Generate Production Ready Code→
This group of generators is tightly coupled with a specific programming language or development framework. They operate at a lower level, automating common boilerplate code within that ecosystem.
In the .NET ecosystem, source generators are features that hook into the C# compiler to inspect code and generate new source files on the fly during compilation.
Common Applications:
Implementing property change notifications (e.g., INotifyPropertyChanged).
Generating optimized serialization and deserialization logic.
Automating boilerplate for record types and equality methods.
Injecting cross-cutting concerns like logging or validation.
Technical Implementation:
Source generators, built on the Roslyn compiler platform, can analyze syntax nodes, class declarations, and access modifiers to inform code generation. Modern incremental generators only execute when relevant code changes are made to maintain build performance. They are typically built as .NET Standard 2.0 assemblies and configured within a project's .csproj
file. Source generators often work alongside Analyzers, which enforce coding standards and provide diagnostics directly in the IDE.
Flutter’s build_runner
The build_runner package serves a similar purpose in the Dart and Flutter communities. It is a build system that can be configured to run code generators over a project’s source files. Developers use it for:
JSON Serialization: The json_serializable package generates code for converting Dart objects to and from JSON.
Dependency Injection: Tools like injectable use build_runner to generate dependency injection containers.
Immutable States: Packages for state management can generate immutable data classes.
These tools use a formal specification, such as an API contract or a schema definition, as their primary input. They parse this specification to produce code, helping to maintain consistency between separate system components.
OpenAPI Generator
The OpenAPI Generator is a popular tool for web development. It takes an OpenAPI Specification (formerly Swagger) file as input and can produce many outputs. This is particularly useful for building applications that communicate over REST APIs. Its capabilities include:
Generating API client libraries in dozens of programming languages.
Creating server stubs for various backend frameworks.
Producing API documentation.
This automation keeps the client and server code synchronized with the API definition.
Visual Studio provides comprehensive support for source generators, streamlining the process of creating, debugging, and using them directly within the IDE.
Project Configuration: Developers can enable source generator functionality by adding specific NuGet packages and configuring the project's .csproj file.
Real-time IDE Support: The integration offers a rich editing experience for both hand-written and generated code, including:
â—¦ Syntax highlighting
â—¦ IntelliSense and code completion
â—¦ Go to Definition
Seamless Workflow: This workflow allows developers to inspect user code and generate boilerplate or additional files as a direct part of the build process.
Debugging: Visual Studio provides specialized tools to debug the source generator's execution as it runs during compilation.
Roslyn API Integration: Developers can fully utilize the Roslyn compiler APIs to inspect syntax, analyze semantics, and create powerful, context-aware generators.
Multi-language Support: The platform is flexible enough to support scenarios where a C# source generator produces code for other languages, such as C, within the same project.
Increased Productivity: Automates repetitive tasks and streamlines the implementation of cross-cutting concerns like logging or data binding.
Simplified Management: This feature makes it easy to manage and inspect all source files, including those produced by the generator.
Familiar Environment: This feature enables developers to build, test, and maintain complex code generation tasks without leaving the familiar Visual Studio ecosystem.
Simplicity: Keep the generator logic focused on a single, well-defined task. Complex generators can be difficult to maintain.
Code Conventions: Generated code should follow the same naming and structure patterns as the handwritten code in the project. Using partial classes to extend existing types is a common technique.
Performance: A generator that processes every file can slow down builds for large codebases. Incremental generation techniques can mitigate this by regenerating only the affected parts of the code.
Guidelines from the .NET team: The .NET team recommends using source generation to automate code creation, not to introduce or replace new language features. Source generators are designed to generate code during compilation, but should not be used to add language features or perform code rewriting, as this can complicate language evolution and maintenance. Always follow best practices and guidance provided by the .NET team when implementing source generation.
Developing source generators requires robust methods for debugging their execution and logging behavior, as they run inside the compiler process.
Visual Studio provides a powerful, built-in experience for debugging source generators. The key is to configure the project to launch the C# compiler (csc.exe) and attach the debugger.
Configuration: This is typically done by modifying the launchSettings.json file in the generator project. Visual Studio often provides a default profile for this.
launchSettings.json Example:
This profile tells Visual Studio to start the compiler, passing a target project to be built as a command-line argument.
1{ 2 "profiles": { 3 "Generator Debugger": { 4 "commandName": "DebugRoslynComponent", 5 "targetProject": "../MyAwesomeApp/MyAwesomeApp.csproj" 6 } 7 } 8}
Execution Flow:
Select the Generator Debugger profile in Visual Studio.
Set breakpoints inside your generator's Initialize or Execute methods.
Start debugging (F5). Visual Studio will launch the compiler, and your breakpoints will be hit, allowing you to inspect variables and step through the code generation logic just like any other C# code.
Due to security sandboxing, a source generator cannot directly write to the file system. The correct way to "log" information or report errors is by using diagnostics.
Mechanism: The GeneratorExecutionContext or SourceProductionContext provides a ReportDiagnostic method. These diagnostics appear in the Visual Studio Error List window during a build.
Creating a Diagnostic: A diagnostic consists of a unique ID, a message, a severity level (Info, Warning, Error), and an optional code location.
Code Snippet: Reporting an Informational Diagnostic
This code, placed inside a generator's Execute method, creates a diagnostic message that will appear in the build output.
1public void Execute(GeneratorExecutionContext context) 2{ 3 // ... generator logic ... 4 5 // Create a descriptor for our diagnostic 6 var descriptor = new DiagnosticDescriptor( 7 id: "SG001", 8 title: "Generator Run", 9 messageFormat: "The XYZ source generator was executed successfully.", 10 category: "SourceGenerator", 11 defaultSeverity: DiagnosticSeverity.Info, 12 isEnabledByDefault: true); 13 14 // Report the diagnostic 15 context.ReportDiagnostic(Diagnostic.Create(descriptor, Location.None)); 16 17 // ... rest of the generator logic ... 18}
Hands-on projects are an excellent way to understand the practical applications and workflow of creating source generators.
Practical Experience: Move from theory to practice by building a functional generator.
Workflow Mastery: Learn the end-to-end process, from project setup and configuration to generating final code.
API Exploration: Gain proficiency with the Roslyn APIs for syntax tree inspection and manipulation.
Discover Best Practices: Understand effective patterns for creating efficient and maintainable generators.
Here are some common and valuable source generator projects to build:
Auto INotifyPropertyChanged:
â—¦ Goal: Automatically implement the INotifyPropertyChanged interface for classes decorated with a specific attribute.
â—¦ Benefit: Reduces a large amount of repetitive boilerplate code in MVVM applications.
Builder Pattern Generator:
â—¦ Goal: For a given complex class, generate a separate Builder class that provides a fluent API for object construction.
â—¦ Example:
1// Before: Manually create a complex object 2var user = new User("John", "Doe", "john.doe@email.com", true); 3 4// After: Use a generated builder 5var user = new UserBuilder() 6 .WithFirstName("John") 7 .WithLastName("Doe") 8 .WithEmail("john.doe@email.com") 9 .IsActive(true) 10 .Build();
Strongly-Typed Wrapper Generator:
â—¦ Goal: Generate strongly-typed wrapper structs or classes for primitive types (e.g., creating a CustomerId struct that wraps an int).
â—¦ Benefit: Improves type safety by preventing accidental assignment between different kinds of IDs (e.g., a CustomerId cannot be assigned to an OrderId).
External File Parser:
â—¦ Goal: Read a non-C# file included in the project (like a JSON, XML, or RESX file) and generate strongly-typed C# classes representing its contents.
â—¦ Benefit: Provides compile-time safety and IntelliSense for accessing data from structured text files.
Support for compile-time code generation continues to grow across programming languages, aiming to improve developer productivity and application performance. The next step in this progression involves more sophisticated frameworks and the integration of machine learning, which could enable generators to understand code intent more deeply.
As these tools mature, they become a standard part of modern development, allowing teams to focus on unique business challenges instead of repetitive coding.