C# > Source Generators > Using Roslyn for Code Generation > Creating a Source Generator
Attribute-Driven Property Generator
This example demonstrates a source generator that automatically creates properties based on a custom attribute applied to a class. This simplifies the process of defining properties and reduces boilerplate code.
Concepts Behind the Snippet
This source generator utilizes a custom attribute, `GeneratePropertyAttribute`, to mark fields that should have properties automatically generated. The generator then analyzes the code, identifies fields marked with the attribute, and generates the corresponding properties with getter and setter implementations. This showcases a more practical use of source generators for reducing boilerplate and enhancing code maintainability.
Define the Attribute
First, we define the `GeneratePropertyAttribute`. This attribute can be applied to fields. It has an optional `PropertyName` property which allows the user to specify the name of the generated property. If not specified, the property name will be derived from the field name.
using System;
namespace MySourceGenerator
{
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class GeneratePropertyAttribute : Attribute
{
public string PropertyName { get; set; }
public GeneratePropertyAttribute(string propertyName = null)
{
PropertyName = propertyName;
}
}
}
Implement the Source Generator
This code defines the `PropertyGenerator`. The `Execute` method finds classes containing fields decorated with `GeneratePropertyAttribute`. For each such field, it generates a corresponding property. It extracts the property name either from the attribute's argument or by capitalizing the field name. The generated property is then added to the compilation. It handles the case where the `GenerateProperty` attribute may or may not have an argument.
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System.Linq;
using System.Text;
namespace MySourceGenerator
{
[Generator]
public class PropertyGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// Find all classes with fields marked with the GenerateProperty attribute
var classes = context.Compilation.SyntaxTrees
.SelectMany(tree => tree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>())
.Where(c => c.Members.Any(m => m.Kind() == Microsoft.CodeAnalysis.CSharp.SyntaxKind.FieldDeclaration && ((FieldDeclarationSyntax)m).AttributeLists.Any(a => a.ToString().Contains("GenerateProperty"))));
foreach (var classDeclaration in classes)
{
var namespaceName = (classDeclaration.Parent as NamespaceDeclarationSyntax)?.Name.ToString() ?? "";
var className = classDeclaration.Identifier.Text;
var fieldsToGenerate = classDeclaration.Members
.OfType<FieldDeclarationSyntax>()
.Where(f => f.AttributeLists.Any(a => a.ToString().Contains("GenerateProperty")))
.Select(f => new
{
FieldDeclaration = f,
FieldName = f.Declaration.Variables.First().Identifier.Text,
FieldType = f.Declaration.Type.ToString(),
Attribute = f.AttributeLists.SelectMany(a => a.Attributes).FirstOrDefault(attr => attr.Name.ToString() == "GenerateProperty")
}).ToList();
var sourceBuilder = new StringBuilder($"""
namespace {namespaceName}
{{
public partial class {className}
{{
""");
foreach (var field in fieldsToGenerate)
{
string propertyName;
if (field.Attribute?.ArgumentList != null && field.Attribute.ArgumentList.Arguments.Any())
{
propertyName = field.Attribute.ArgumentList.Arguments.First().Expression.ToString().Trim('"');
}
else
{
// Create property name from field name
propertyName = char.ToUpper(field.FieldName[0]) + field.FieldName.Substring(1);
}
sourceBuilder.AppendLine($" public {field.FieldType} {propertyName} {{ get; set; }}");
}
sourceBuilder.AppendLine($"""
}}
}}
""");
context.AddSource($"{className}.Generated.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
}
public void Initialize(GeneratorInitializationContext context)
{
// No initialization required for this example.
}
}
}
Example Usage
Here's how you would use the attribute. `myField` will get a property named `MyField`. `anotherField` will get a property named `CustomName`. Note the use of `partial class` which is important so the generated code can be merged with the existing class definition.
namespace MyNamespace
{
public partial class MyClass
{
[GenerateProperty]
private string myField;
[GenerateProperty("CustomName")]
private int anotherField;
}
}
Real-Life Use Case
This attribute-driven property generator streamlines the process of creating properties for data transfer objects (DTOs) or view models. By simply adding the `GenerateProperty` attribute to fields, developers can automatically generate the necessary properties, reducing boilerplate code and improving code maintainability.
Best Practices
Interview Tip
Be prepared to discuss how attribute-driven code generation can improve code maintainability and reduce boilerplate. Explain the benefits of using source generators for tasks that involve repetitive code generation.
When to Use Them
Use attribute-driven code generation when you need to generate code based on specific attributes or metadata associated with code elements. This is particularly useful for scenarios where you want to automate the creation of properties, methods, or other code constructs based on specific configurations or annotations.
Memory Footprint
The memory footprint of the generator itself is minimal. The generated properties will increase the overall size of the class but typically not by a significant amount unless a large number of properties are generated. Optimize the generated code to avoid unnecessary memory allocations.
Alternatives
Alternatives include:
Pros
Cons
FAQ
-
How do I handle errors in my source generator?
Use the `GeneratorExecutionContext.ReportDiagnostic` method to report errors or warnings to the user. Provide informative messages that help the user understand and resolve the issue. -
How can I customize the generated code?
Provide options for customizing the generated code, such as allowing users to specify property access modifiers, add validation logic, or modify the naming conventions. Use attributes or configuration files to allow users to configure the generator. -
How do I test my source generator?
Create unit tests that verify the generated code under various conditions. Use the `CSharpGeneratorDriver` class to simulate the compilation process and assert that the generated code is correct.