前置知识: C#

源生成器

4 minAdvanced2026/6/14

C# Source Generators

概述

生成器(Source Generators)是 C# 9 引入的编译时代码生成技术。它允许你在编译阶段分析现有代码,并自动生成额外的 C# 源文件,这些生成的代码会与原始代码一起参与编译。源生成器不会修改已有代码,而是向编译过程中添加新代码。

为什么需要源生成器?传统上,C# 开发者使用反射在运行时获取型信息,但反射有性能开销。源生成器将这工作提前到编译期完成,生成的代码与手写代码一样高效。它还能减少重复的样板代码,比如自动实现 INotifyPropertyChanged、自动注册依赖注入等。

基础概念

ISourceGenerator:源生成器需要实现 ISourceGenerator 接口,包含两个方法:Initialize 用于注册语法接收器,Execute 用于执行代码生成逻辑。

GeneratorAttribute:每个源生成器必须标注 [Generator] 特性,编译器才能识别它。

语法接收器:通过 ISyntaxReceiver 可以在编译阶段遍历语法树,筛选出感兴趣的语法节点,供 Execute 方法使用。

增量源生成器:.NET 6 引入了 IIncrementalGenerator 接口,它只重新处理变化的部分,大幅提升编译性能。新项目推荐使用增量源生成器

不修改已有代码:源生成器只能添加新代码,不能修改已有的源文件。这是一个核心设计原则。

快速上手

创建一个最简单的源生成器项目:

# 创建源生成器类库项目
dotnet new classlib -n MySourceGenerator

# 进入项目目录
cd MySourceGenerator

编辑项目文件,设置必要的属性:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <!-- 源生成器必须是 netstandard2.0 -->
    <TargetFramework>netstandard2.0</TargetFramework>
    <!-- 不自动生成 AssemblyInfo -->
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <!-- 引入 Roslyn 分析器包 -->
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
  </ItemGroup>
</Project>

最简单的源生成器示例:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;

namespace MySourceGenerator;

[Generator]
public class HelloGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // 初始化阶段,可以注册语法接收器
        // 这里不需要,留空即可
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // 生成一个简单的类
        var source = @"
namespace Generated;

public static class HelloHelper
{
    public static string Greet(string name) => $""你好, {name}!"";
}
";
        // 添加生成的源代码
        context.AddSource("HelloHelper.g.cs", SourceText.From(source, Encoding.UTF8));
    }
}

在目标项目中引用生成器

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <!-- 引用源生成器项目,注意要加 OutputItemType 和 ReferenceOutputAssembly -->
    <ProjectReference Include="..\MySourceGenerator\MySourceGenerator.csproj"
                      OutputItemType="Analyzer"
                      ReferenceOutputAssembly="false" />
  </ItemGroup>
</Project>

引用后就可以在代码中使用生成的

// 使用源生成器生成的代码
Console.WriteLine(Generated.HelloHelper.Greet("世界"));
// 输出: 你好, 世界!

详细用法

使用语法接收器筛选目标

当需要只对特定或方法生成代码时,使用语法接收器:

// 定义一个标记特性
[AttributeUsage(AttributeTargets.Class)]
public class AutoNotifyAttribute : Attribute
{
}

// 语法接收器:收集标注了 [AutoNotify] 的类
public class AutoNotifyReceiver : ISyntaxReceiver
{
    // 存储收集到的类声明
    public List<ClassDeclarationSyntax> CandidateClasses { get; } = new();

    // 遍历每个语法节点
    public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
    {
        // 只关注类声明
        if (syntaxNode is ClassDeclarationSyntax classDecl)
        {
            // 检查是否有 [AutoNotify] 特性
            var hasAttribute = classDecl.AttributeLists
                .SelectMany(al => al.Attributes)
                .Any(a => a.Name.ToString().Contains("AutoNotify"));

            if (hasAttribute)
            {
                CandidateClasses.Add(classDecl);
            }
        }
    }
}

// 源生成器
[Generator]
public class AutoNotifyGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        // 注册语法接收器
        context.RegisterForSyntaxNotifications(() => new AutoNotifyReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        // 获取语法接收器收集的结果
        if (context.SyntaxReceiver is not AutoNotifyReceiver receiver)
            return;

        foreach (var classDecl in receiver.CandidateClasses)
        {
            // 获取类的模型
            var model = context.Compilation.GetSemanticModel(classDecl.SyntaxTree);
            var classSymbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
            if (classSymbol is null) continue;

            // 获取命名空间
            var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
            var className = classSymbol.Name;

            // 收集所有字段
            var fields = classDecl.Members
                .OfType<FieldDeclarationSyntax>()
                .Where(f => f.Declaration.Variables.Count == 1);

            var sb = new StringBuilder();
            sb.AppendLine("// <auto-generated/>");
            sb.AppendLine("using System.ComponentModel;");
            sb.AppendLine();
            sb.AppendLine($"namespace {namespaceName};");
            sb.AppendLine();
            sb.AppendLine($"public partial class {className} : INotifyPropertyChanged");
            sb.AppendLine("{");
            sb.AppendLine("    public event PropertyChangedEventHandler? PropertyChanged;");
            sb.AppendLine();
            sb.AppendLine("    protected void OnPropertyChanged(string name) =>");
            sb.AppendLine("        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));");

            // 为每个字段生成属性
            foreach (var field in fields)
            {
                var variable = field.Declaration.Variables.First();
                var fieldName = variable.Identifier.Text;
                var fieldType = field.Declaration.Type.ToString();

                // 将 _name 转为 Name
                var propName = fieldName.TrimStart('_');
                if (propName.Length > 0)
                    propName = char.ToUpper(propName[0]) + propName.Substring(1);

                sb.AppendLine();
                sb.AppendLine($"    public {fieldType} {propName}");
                sb.AppendLine("    {");
                sb.AppendLine($"        get => {fieldName};");
                sb.AppendLine("        set");
                sb.AppendLine("        {");
                sb.AppendLine($"            if ({fieldName} != value)");
                sb.AppendLine("            {");
                sb.AppendLine($"                {fieldName} = value;");
                sb.AppendLine($"                OnPropertyChanged(nameof({propName}));");
                sb.AppendLine("            }");
                sb.AppendLine("        }");
                sb.AppendLine("    }");
            }

            sb.AppendLine("}");

            context.AddSource($"{className}.g.cs",
                SourceText.From(sb.ToString(), Encoding.UTF8));
        }
    }
}

使用这个生成器

// 标记需要生成代码的类
[AutoNotify]
public partial class ViewModel
{
    // 私有字段,源生成器会自动生成对应的公共属性
    private string _name = "";
    private int _age = 0;
    private bool _isActive = false;
}

// 编译后,生成的代码等价于:
public partial class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

    public string Name
    {
        get => _name;
        set { if (_name != value) { _name = value; OnPropertyChanged(nameof(Name)); } }
    }

    public int Age
    {
        get => _age;
        set { if (_age != value) { _age = value; OnPropertyChanged(nameof(Age)); } }
    }

    public bool IsActive
    {
        get => _isActive;
        set { if (_isActive != value) { _isActive = value; OnPropertyChanged(nameof(IsActive)); } }
    }
}

增量源生成器

增量源生成器只重新处理变化的部分,性能更好:

using Microsoft.CodeAnalysis;

[Generator]
public class IncrementalHelloGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 定义一个提供器:筛选带有特定特性的类
        var classProvider = context.SyntaxProvider
            .CreateSyntaxProvider(
                // 快速判断:是否是类声明
                predicate: (node, _) => node is ClassDeclarationSyntax,
                // 详细转换:提取类信息
                transform: (ctx, _) =>
                {
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    var model = ctx.SemanticModel;
                    var symbol = model.GetDeclaredSymbol(classDecl) as INamedTypeSymbol;
                    return symbol?.ToDisplayString();
                })
            .Where(name => name is not null);

        // 注册生成逻辑
        context.RegisterSourceOutput(classProvider, (productionContext, className) =>
        {
            var source = $@"
// <auto-generated/>
namespace Generated;

public static class {className!.Replace('.', '_')}Extensions
{{
    public static string GetTypeName() => ""{className}"";
}}
";
            productionContext.AddSource($"{className}.g.cs", source);
        });
    }
}

从外部文件生成代码

生成器可以读取项目中的额外文件来生成代码:

[Generator]
public class ConfigGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
        // 读取项目中的额外文件
        foreach (var file in context.AdditionalFiles)
        {
            // 只处理 .json 配置文件
            if (!file.Path.EndsWith(".json"))
                continue;

            var content = File.ReadAllText(file.Path);
            // 解析 JSON 并生成对应的 C# 类
            // 这里简化处理,实际应使用 System.Text.Json
            var className = Path.GetFileNameWithoutExtension(file.Path);

            var source = $@"
// <auto-generated/>
// 从 {file.Path} 生成

public static class {className}Config
{{
    public static string RawJson => @""{content.Replace("\"", "\"\"")}"";
}}
";
            context.AddSource($"{className}Config.g.cs",
                SourceText.From(source, Encoding.UTF8));
        }
    }
}

在项目文件中声明额外文件:

<ItemGroup>
  <AdditionalFiles Include="appsettings.json" />
</ItemGroup>

常见场景

自动注册依赖注入

// 定义标记特性
[AttributeUsage(AttributeTargets.Class)]
public class RegisterServiceAttribute : Attribute
{
    public ServiceLifetime Lifetime { get; }
    public RegisterServiceAttribute(ServiceLifetime lifetime = ServiceLifetime.Scoped)
    {
        Lifetime = lifetime;
    }
}

// 源生成器自动生成注册代码
[Generator]
public class ServiceRegistrationGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        var services = context.SyntaxProvider
            .ForAttributeWithMetadataName(
                "MyApp.RegisterServiceAttribute",
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) =>
                {
                    var classSymbol = (INamedTypeSymbol)ctx.TargetSymbol;
                    var attr = ctx.Attributes.First();
                    var lifetime = (int)attr.ConstructorArguments[0].Value!;
                    return (classSymbol.ToDisplayString(), lifetime);
                });

        context.RegisterSourceOutput(services, (ctx, items) =>
        {
            // 生成服务注册扩展方法
            var sb = new StringBuilder();
            sb.AppendLine("// <auto-generated/>");
            sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
            sb.AppendLine();
            sb.AppendLine("public static class AutoServiceExtensions");
            sb.AppendLine("{");
            sb.AppendLine("    public static void AddAutoServices(this IServiceCollection services)");
            sb.AppendLine("    {");

            foreach (var (typeName, lifetime) in items)
            {
                var method = lifetime switch
                {
                    0 => "AddTransient",
                    1 => "AddScoped",
                    2 => "AddSingleton",
                    _ => "AddTransient"
                };
                sb.AppendLine($"        services.{method}<{typeName}>();");
            }

            sb.AppendLine("    }");
            sb.AppendLine("}");

            ctx.AddSource("AutoServiceExtensions.g.cs", sb.ToString());
        });
    }
}

生成强型配置

// 从 JSON 配置文件生成强类型配置类
[Generator]
public class StronglyTypedConfigGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context) { }

    public void Execute(GeneratorExecutionContext context)
    {
        foreach (var file in context.AdditionalFiles)
        {
            if (!file.Path.EndsWith(".settings.json"))
                continue;

            var name = Path.GetFileNameWithoutExtension(file.Path)
                .Replace(".settings", "");
            var content = File.ReadAllText(file.Path);

            // 简化处理:生成包含原始 JSON 的静态类
            var source = $@"
// <auto-generated/>
public static class {name}Settings
{{
    public const string Json = @""{content.Replace("\"", "\"\"")}"";
}}
";
            context.AddSource($"{name}Settings.g.cs",
                SourceText.From(source, Encoding.UTF8));
        }
    }
}

注意事项

目标框架必须是 netstandard2.0:源生成器项目必须使用 netstandard2.0 目标框架,因为它运行在编译器内部,而编译器使用的是 .NET Framework。

不要在生成器中抛出异常生成器中抛出的异常会导致编译失败。使用 context.ReportDiagnostic 报告警告或错误,而不是抛出异常。

生成代码的文件名:建议使用 .g.cs 后缀命名生成的文件,这样开发者能区分手写代码和生成代码。

调试源生成器:调试源生成器需要在项目文件中添加 <IsRoslynComponent>true</IsRoslynComponent>,然后使用 Visual Studio 附加到编译器进程。

增量生成器的缓存:增量源生成器通过管道缓存中间结果。确保你的转换函数是纯函数,不依赖外部状态,否则缓存会导致错误。

进阶用法

生成多文件

// 一次 Execute 中可以生成多个文件
public void Execute(GeneratorExecutionContext context)
{
    // 生成接口
    context.AddSource("IRepository.g.cs", SourceText.From(@"
public interface IRepository<T> where T : class
{
    T? GetById(int id);
    List<T> GetAll();
    void Add(T entity);
    void Update(T entity);
    void Delete(int id);
}
", Encoding.UTF8));

    // 生成实现
    context.AddSource("InMemoryRepository.g.cs", SourceText.From(@"
using System.Collections.Generic;

public class InMemoryRepository<T> : IRepository<T> where T : class
{
    private readonly List<T> _items = new();
    private int _nextId = 1;

    public T? GetById(int id) => _items.Count > id ? _items[id] : null;
    public List<T> GetAll() => _items;
    public void Add(T entity) => _items.Add(entity);
    public void Update(T entity) { /* 简化实现 */ }
    public void Delete(int id) { /* 简化实现 */ }
}
", Encoding.UTF8));
}

报告诊断信息

// 定义自定义诊断规则
private static readonly DiagnosticDescriptor InvalidFieldRule = new(
    id: "MYGEN001",
    title: "字段命名不符合规范",
    messageFormat: "字段 '{0}' 应以下划线开头",
    category: "Naming",
    defaultSeverity: DiagnosticSeverity.Warning,
    isEnabledByDefault: true);

// 在 Execute 中报告诊断
public void Execute(GeneratorExecutionContext context)
{
    foreach (var field in fields)
    {
        var name = field.Declaration.Variables.First().Identifier.Text;
        if (!name.StartsWith("_"))
        {
            // 报告警告而不是抛出异常
            var location = field.GetLocation();
            context.ReportDiagnostic(Diagnostic.Create(InvalidFieldRule, location, name));
        }
    }
}