Skip to content

增量生成器基础概念和示例

本文档介绍增量生成器的基础概念、为什么需要它以及基本使用方法。

📋 文档信息

  • 难度级别: 中级
  • 预计阅读时间: 20 分钟

澧為噺鐢熸垚鍣ㄧず渚?

椤圭洰浣嶇疆

03-IncrementalGenerator/

瀛︿範鐩爣

  • 浣跨敤 IIncrementalGenerator 鎺ュ彛
  • 鐞嗚В澧為噺绠¢亾鎿嶄綔
  • 瀛︿範鎬ц兘浼樺寲鎶€宸?

涓轰粈涔堥渶瑕佸閲忕敓鎴愬櫒锛?

浼犵粺鐨?ISourceGenerator 鍦ㄦ瘡娆$紪璇戞椂閮戒細閲嶆柊鎵ц锛屽嵆浣夸唬鐮佹病鏈夊彉鍖栥€傚閲忕敓鎴愬櫒鍙互锛?

  • 鉁?缂撳瓨鏈彉鍖栫殑缁撴灉
  • 鉁?鍙鐞嗗彉鍖栫殑閮ㄥ垎
  • 鉁?鏄捐憲鎻愰珮缂栬瘧閫熷害

鎬ц兘瀵规瘮

鍦烘櫙ISourceGeneratorIIncrementalGenerator
棣栨缂栬瘧1000ms1000ms
鏃犲彉鍖栭噸缂栬瘧1000ms10ms 鈿?
灏忔敼鍔ㄩ噸缂栬瘧1000ms100ms 鈿?

鐢熸垚鍣ㄤ唬鐮?

csharp
using Microsoft.CodeAnalysis;

[Generator]
public class IncrementalGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. 鍒涘缓绠¢亾锛氭煡鎵炬墍鏈夌被澹版槑
        var classDeclarations = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => node is ClassDeclarationSyntax,
                transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node
            );

        // 2. 娉ㄥ唽浠g爜鐢熸垚
        context.RegisterSourceOutput(
            classDeclarations,
            (spc, classDecl) =>
            {
                var className = classDecl.Identifier.Text;
                var source = $@"
namespace Generated
{{
    public static class {className}Extensions
    {{
        public static string GetInfo(this {className} obj)
            => ""Info for {className}"";
    }}
}}";
                spc.AddSource($"{className}Extensions.g.cs", source);
            });
    }
}

澧為噺绠¢亾鎿嶄綔

1. CreateSyntaxProvider

杩囨护鍜岃浆鎹㈣娉曡妭鐐癸細

csharp
var provider = context.SyntaxProvider.CreateSyntaxProvider(
    predicate: (node, _) => /* 蹇€熻繃婊?*/,
    transform: (ctx, _) => /* 杞崲鑺傜偣 */
);

2. Select

杞崲鏁版嵁锛?

csharp
var transformed = provider.Select((item, _) => 
    new { Name = item.Identifier.Text }
);

3. Where

杩囨护鏁版嵁锛?

csharp
var filtered = provider.Where(item => 
    item.Modifiers.Any(SyntaxKind.PublicKeyword)
);

4. Collect

鏀堕泦鎵€鏈夌粨鏋滐細

csharp
var collected = provider.Collect();

5. Combine

缁勫悎澶氫釜绠¢亾锛?

csharp
var combined = provider1.Combine(provider2);

瀹屾暣绀轰緥

csharp
[Generator]
public class AttributeGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // 1. 鏌ユ壘甯︽湁鐗瑰畾鐗规€х殑绫?
        var classesWithAttribute = context.SyntaxProvider
            .CreateSyntaxProvider(
                predicate: (node, _) => 
                    node is ClassDeclarationSyntax cls &&
                    cls.AttributeLists.Count > 0,
                transform: (ctx, _) =>
                {
                    var classDecl = (ClassDeclarationSyntax)ctx.Node;
                    var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
                    
                    // 妫€鏌ユ槸鍚︽湁鐩爣鐗规€?
                    var hasAttribute = symbol?.GetAttributes()
                        .Any(a => a.AttributeClass?.Name == "GenerateAttribute") 
                        ?? false;
                    
                    return hasAttribute ? symbol : null;
                }
            )
            .Where(symbol => symbol != null);

        // 2. 鐢熸垚浠g爜
        context.RegisterSourceOutput(
            classesWithAttribute,
            (spc, symbol) =>
            {
                var source = GenerateCode(symbol);
                spc.AddSource($"{symbol.Name}.g.cs", source);
            });
    }

    private string GenerateCode(INamedTypeSymbol symbol)
    {
        return $@"
namespace {symbol.ContainingNamespace}
{{
    partial class {symbol.Name}
    {{
        public string GeneratedProperty {{ get; set; }}
    }}
}}";
    }
}

缂撳瓨鏈哄埗

澧為噺鐢熸垚鍣ㄤ娇鐢?IEquatable<T> 鏉ュ垽鏂暟鎹槸鍚﹀彉鍖栵細

csharp
public record ClassInfo(string Name, string Namespace) : IEquatable<ClassInfo>;

var provider = context.SyntaxProvider
    .CreateSyntaxProvider(
        predicate: (node, _) => node is ClassDeclarationSyntax,
        transform: (ctx, _) =>
        {
            var cls = (ClassDeclarationSyntax)ctx.Node;
            var symbol = ctx.SemanticModel.GetDeclaredSymbol(cls);
            return new ClassInfo(
                symbol.Name,
                symbol.ContainingNamespace.ToDisplayString()
            );
        }
    );

鎬ц兘浼樺寲鎶€宸?

鉁?鎺ㄨ崘

csharp
// 1. 鍦?predicate 涓揩閫熻繃婊?
predicate: (node, _) => 
    node is ClassDeclarationSyntax cls &&
    cls.AttributeLists.Count > 0,  // 蹇€熸鏌?

// 2. 浣跨敤 record 绫诲瀷瀹炵幇 IEquatable
public record ClassData(...) : IEquatable<ClassData>;

// 3. 閬垮厤鍦?transform 涓仛澶嶆潅璁$畻
transform: (ctx, _) => 
    new SimpleData(ctx.Node.Identifier.Text)  // 绠€鍗曟暟鎹?

鉂?閬垮厤

csharp
// 1. 涓嶈鍦?predicate 涓娇鐢ㄨ涔夋ā鍨?
predicate: (node, ct) => 
{
    var model = /* 鑾峰彇璇箟妯″瀷 */;  // 鎱紒
    return /* ... */;
}

// 2. 涓嶈杩斿洖澶嶆潅瀵硅薄
transform: (ctx, _) => 
    new ComplexObject { /* 寰堝灞炴€?*/ }  // 闅句互缂撳瓨

杩愯绀轰緥

bash
cd 03-IncrementalGenerator
dotnet build
dotnet run --project IncrementalGenerator.Consumer

涓嬩竴姝?

  • 鏌ョ湅澧為噺绠¢亾 API
  • [鏌ョ湅鎬ц兘浼樺寲鎶€宸(/api/advanced-patterns)
  • [ToString 鐢熸垚鍣ㄧず渚媇(/examples/tostring-generator)

澧為噺鐢熸垚鍣ㄥ師鐞?

浼犵粺鐢熸垚鍣?vs 澧為噺鐢熸垚鍣?

澧為噺绠¢亾宸ヤ綔娴佺▼


瀹屾暣椤圭洰缁撴瀯

鐩綍缁撴瀯

03-IncrementalGenerator/
鈹溾攢鈹€ IncrementalGenerator.slnx
鈹溾攢鈹€ IncrementalGenerator.Generator/
鈹?  鈹溾攢鈹€ IncrementalGenerator.Generator.csproj
鈹?  鈹溾攢鈹€ IncrementalGenerator.cs
鈹?  鈹斺攢鈹€ Models/
鈹?      鈹斺攢鈹€ ClassInfo.cs
鈹斺攢鈹€ IncrementalGenerator.Consumer/
    鈹溾攢鈹€ IncrementalGenerator.Consumer.csproj
    鈹溾攢鈹€ Program.cs
    鈹斺攢鈹€ Models/
        鈹斺攢鈹€ Person.cs

椤圭洰閰嶇疆

IncrementalGenerator.Generator.csproj:

xml
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <LangVersion>latest</LangVersion>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
  </ItemGroup>

</Project>

澧為噺绠¢亾璇﹁В

绠¢亾鎿嶄綔绗?

1. CreateSyntaxProvider - 璇硶杩囨护

csharp
public class SyntaxProviderExamples
{
    public void BasicExample(IncrementalGeneratorInitializationContext context)
    {
        // 鍩虹鐢ㄦ硶锛氭煡鎵炬墍鏈夌被澹版槑
        var classes = context.SyntaxProvider.CreateSyntaxProvider(
            predicate: static (node, _) => node is ClassDeclarationSyntax,
            transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node
        );
    }
    
    public void WithAttributeFilter(IncrementalGeneratorInitializationContext context)
    {
        // 楂樼骇鐢ㄦ硶锛氭煡鎵惧甫鐗规€х殑绫?
        var classesWithAttribute = context.SyntaxProvider.CreateSyntaxProvider(
            // 绗竴闃舵锛氬揩閫熻娉曡繃婊わ紙涓嶄娇鐢ㄨ涔夋ā鍨嬶級
            predicate: static (node, _) =>
            {
                // 鍙鏌ヨ娉曪紝闈炲父蹇?
                if (node is not ClassDeclarationSyntax classDecl)
                    return false;
                
                // 蹇€熸鏌ユ槸鍚︽湁鐗规€?
                return classDecl.AttributeLists.Count > 0;
            },
            
            // 绗簩闃舵锛氳涔夎繃婊わ紙浣跨敤璇箟妯″瀷锛?
            transform: static (ctx, _) =>
            {
                var classDecl = (ClassDeclarationSyntax)ctx.Node;
                var symbol = ctx.SemanticModel.GetDeclaredSymbol(classDecl);
                
                if (symbol == null)
                    return null;
                
                // 妫€鏌ユ槸鍚︽湁鐩爣鐗规€?
                var hasAttribute = symbol.GetAttributes()
                    .Any(a => a.AttributeClass?.Name == "GenerateBuilderAttribute");
                
                if (!hasAttribute)
                    return null;
                
                // 杩斿洖闇€瑕佺殑淇℃伅
                return new ClassInfo(
                    Name: symbol.Name,
                    Namespace: symbol.ContainingNamespace.ToDisplayString(),
                    Properties: symbol.GetMembers()
                        .OfType<IPropertySymbol>()
                        .Where(p => p.DeclaredAccessibility == Accessibility.Public)
                        .Select(p => new PropertyInfo(p.Name, p.Type.ToDisplayString()))
                        .ToImmutableArray()
                );
            }
        );
    }
}

// 鏁版嵁妯″瀷锛堝繀椤诲疄鐜?IEquatable 浠ユ敮鎸佺紦瀛橈級
public record ClassInfo(
    string Name,
    string Namespace,
    ImmutableArray<PropertyInfo> Properties
);

public record PropertyInfo(
    string Name,
    string Type
);

2. Select - 鏁版嵁杞崲

csharp
public void SelectExample(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is ClassDeclarationSyntax,
        transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node
    );
    
    // 杞崲涓虹畝鍗曠殑鍚嶇О鍒楄〃
    var classNames = classes.Select(static (classDecl, _) => 
        classDecl.Identifier.Text
    );
    
    // 杞崲涓烘洿澶嶆潅鐨勬暟鎹粨鏋?
    var classData = classes.Select(static (classDecl, _) => new
    {
        Name = classDecl.Identifier.Text,
        IsPublic = classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)),
        MemberCount = classDecl.Members.Count
    });
}

3. Where - 鏁版嵁杩囨护

csharp
public void WhereExample(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is ClassDeclarationSyntax,
        transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node
    );
    
    // 鍙繚鐣?public 绫?
    var publicClasses = classes.Where(static (classDecl, _) =>
        classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword))
    );
    
    // 鍙繚鐣欐湁鎴愬憳鐨勭被
    var nonEmptyClasses = classes.Where(static (classDecl, _) =>
        classDecl.Members.Count > 0
    );
    
    // 缁勫悎澶氫釜鏉′欢
    var filteredClasses = classes.Where(static (classDecl, _) =>
        classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PublicKeyword)) &&
        classDecl.Members.Count > 0 &&
        !classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.AbstractKeyword))
    );
}

4. Collect - 鏀堕泦缁撴灉

csharp
public void CollectExample(IncrementalGeneratorInitializationContext context)
{
    var classes = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is ClassDeclarationSyntax,
        transform: static (ctx, _) => (ClassDeclarationSyntax)ctx.Node
    );
    
    // 鏀堕泦鎵€鏈夌被鍒颁竴涓暟缁?
    var allClasses = classes.Collect();
    
    // 浣跨敤鏀堕泦鐨勭粨鏋滅敓鎴愬崟涓枃浠?
    context.RegisterSourceOutput(allClasses, static (spc, classes) =>
    {
        var sb = new StringBuilder();
        sb.AppendLine("namespace Generated");
        sb.AppendLine("{");
        sb.AppendLine("    public static class ClassRegistry");
        sb.AppendLine("    {");
        sb.AppendLine("        public static readonly string[] AllClasses = new[]");
        sb.AppendLine("        {");
        
        foreach (var classDecl in classes)
        {
            sb.AppendLine($"            \"{classDecl.Identifier.Text}\",");
        }
        
        sb.AppendLine("        };");
        sb.AppendLine("    }");
        sb.AppendLine("}");
        
        spc.AddSource("ClassRegistry.g.cs", sb.ToString());
    });
}

5. Combine - 缁勫悎绠¢亾

csharp
public void CombineExample(IncrementalGeneratorInitializationContext context)
{
    // 绠¢亾 1锛氭煡鎵炬墍鏈夌被
    var classes = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is ClassDeclarationSyntax,
        transform: static (ctx, _) => ctx.Node.ToString()
    );
    
    // 绠¢亾 2锛氭煡鎵炬墍鏈夋帴鍙?
    var interfaces = context.SyntaxProvider.CreateSyntaxProvider(
        predicate: static (node, _) => node is InterfaceDeclarationSyntax,
        transform: static (ctx, _) => ctx.Node.ToString()
    );
    
    // 缁勫悎涓や釜绠¢亾
    var combined = classes.Collect().Combine(interfaces.Collect());
    
    // 浣跨敤缁勫悎鐨勭粨鏋?
    context.RegisterSourceOutput(combined, static (spc, data) =>
    {
        var (classNames, interfaceNames) = data;
        
        var sb = new StringBuilder();
        sb.AppendLine("namespace Generated");
        sb.AppendLine("{");
        sb.AppendLine("    public static class TypeRegistry");
        sb.AppendLine("    {");
        sb.AppendLine($"        public const int ClassCount = {classNames.Length};");
        sb.AppendLine($"        public const int InterfaceCount = {interfaceNames.Length};");
        sb.AppendLine("    }");
        sb.AppendLine("}");
        
        spc.AddSource("TypeRegistry.g.cs", sb.ToString());
    });
}

鐢熸垚鍣ㄥ疄鐜?

瀹屾暣鐨勫閲忕敓鎴愬櫒绀轰緥

csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
using System.Text;

namespace IncrementalGenerator.Generator
{
    /// <summary>
    /// 涓哄甫鏈?[GenerateBuilder] 鐗规€х殑绫荤敓鎴?Builder 妯″紡浠g爜
    /// </summary>
    [Generator]
    public class BuilderGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            // 1. 鍒涘缓绠¢亾锛氭煡鎵惧甫鏈?GenerateBuilder 鐗规€х殑绫?
            var classDeclarations = context.SyntaxProvider
                .CreateSyntaxProvider(
                    // 绗竴闃舵锛氬揩閫熻娉曡繃婊?
                    predicate: static (node, _) => IsCandidateNode(node),
                    
                    // 绗簩闃舵锛氳涔夎繃婊ゅ拰鏁版嵁鎻愬彇
                    transform: static (ctx, _) => GetClassInfo(ctx)
                )
                .Where(static info => info != null);
            
            // 2. 娉ㄥ唽浠g爜鐢熸垚
            context.RegisterSourceOutput(
                classDeclarations,
                static (spc, classInfo) => GenerateBuilderCode(spc, classInfo!)
            );
        }
        
        /// <summary>
        /// 蹇€熻娉曡繃婊わ細妫€鏌ヨ妭鐐规槸鍚﹀彲鑳芥槸鐩爣绫?
        /// </summary>
        private static bool IsCandidateNode(SyntaxNode node)
        {
            // 鍙鏌ヨ娉曪紝涓嶄娇鐢ㄨ涔夋ā鍨嬶紙闈炲父蹇級
            if (node is not ClassDeclarationSyntax classDecl)
                return false;
            
            // 蹇€熸鏌ワ細鏄惁鏈夌壒鎬?
            if (classDecl.AttributeLists.Count == 0)
                return false;
            
            // 蹇€熸鏌ワ細鏄惁鏄?partial 绫?
            if (!classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
                return false;
            
            return true;
        }
        
        /// <summary>
        /// 璇箟杩囨护锛氭彁鍙栫被淇℃伅
        /// </summary>
        private static ClassInfo? GetClassInfo(GeneratorSyntaxContext context)
        {
            var classDecl = (ClassDeclarationSyntax)context.Node;
            var symbol = context.SemanticModel.GetDeclaredSymbol(classDecl);
            
            if (symbol == null)
                return null;
            
            // 妫€鏌ユ槸鍚︽湁 GenerateBuilder 鐗规€?
            var hasAttribute = symbol.GetAttributes()
                .Any(a => a.AttributeClass?.Name == "GenerateBuilderAttribute");
            
            if (!hasAttribute)
                return null;
            
            // 鎻愬彇灞炴€т俊鎭?
            var properties = symbol.GetMembers()
                .OfType<IPropertySymbol>()
                .Where(p => p.DeclaredAccessibility == Accessibility.Public &&
                           p.SetMethod != null)
                .Select(p => new PropertyInfo(
                    Name: p.Name,
                    Type: p.Type.ToDisplayString()
                ))
                .ToImmutableArray();
            
            return new ClassInfo(
                Name: symbol.Name,
                Namespace: symbol.ContainingNamespace.ToDisplayString(),
                Properties: properties
            );
        }
        
        /// <summary>
        /// 鐢熸垚 Builder 浠g爜
        /// </summary>
        private static void GenerateBuilderCode(
            SourceProductionContext context,
            ClassInfo classInfo)
        {
            var sb = new StringBuilder();
            
            // 鏂囦欢澶?
            sb.AppendLine("// <auto-generated/>");
            sb.AppendLine("#nullable enable");
            sb.AppendLine();
            
            // 鍛藉悕绌洪棿
            sb.AppendLine($"namespace {classInfo.Namespace}");
            sb.AppendLine("{");
            
            // Builder 绫?
            sb.AppendLine($"    public class {classInfo.Name}Builder");
            sb.AppendLine("    {");
            
            // 瀛楁
            foreach (var prop in classInfo.Properties)
            {
                sb.AppendLine($"        private {prop.Type} _{ToCamelCase(prop.Name)};");
            }
            sb.AppendLine();
            
            // With 鏂规硶
            foreach (var prop in classInfo.Properties)
            {
                sb.AppendLine($"        public {classInfo.Name}Builder With{prop.Name}({prop.Type} value)");
                sb.AppendLine("        {");
                sb.AppendLine($"            _{ToCamelCase(prop.Name)} = value;");
                sb.AppendLine("            return this;");
                sb.AppendLine("        }");
                sb.AppendLine();
            }
            
            // Build 鏂规硶
            sb.AppendLine($"        public {classInfo.Name} Build()");
            sb.AppendLine("        {");
            sb.AppendLine($"            return new {classInfo.Name}");
            sb.AppendLine("            {");
            
            for (int i = 0; i < classInfo.Properties.Length; i++)
            {
                var prop = classInfo.Properties[i];
                var comma = i < classInfo.Properties.Length - 1 ? "," : "";
                sb.AppendLine($"                {prop.Name} = _{ToCamelCase(prop.Name)}{comma}");
            }
            
            sb.AppendLine("            };");
            sb.AppendLine("        }");
            
            // 鍏抽棴绫诲拰鍛藉悕绌洪棿
            sb.AppendLine("    }");
            sb.AppendLine("}");
            
            // 娣诲姞婧愭枃浠?
            context.AddSource(
                $"{classInfo.Name}Builder.g.cs",
                sb.ToString()
            );
        }
        
        private static string ToCamelCase(string name)
        {
            if (string.IsNullOrEmpty(name))
                return name;
            
            return char.ToLower(name[0]) + name.Substring(1);
        }
    }
    
    /// <summary>
    /// 绫讳俊鎭紙蹇呴』瀹炵幇 IEquatable 浠ユ敮鎸佺紦瀛橈級
    /// </summary>
    internal record ClassInfo(
        string Name,
        string Namespace,
        ImmutableArray<PropertyInfo> Properties
    );
    
    /// <summary>
    /// 灞炴€т俊鎭?
    /// </summary>
    internal record PropertyInfo(
        string Name,
        string Type
    );
}

浣跨敤绀轰緥

**瀹氫箟鐗规€?*:

csharp
namespace IncrementalGenerator.Consumer
{
    [AttributeUsage(AttributeTargets.Class)]
    public class GenerateBuilderAttribute : Attribute
    {
    }
}

**浣跨敤鐢熸垚鍣?*:

csharp
namespace IncrementalGenerator.Consumer
{
    [GenerateBuilder]
    public partial class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
    }
    
    class Program
    {
        static void Main()
        {
            // 浣跨敤鐢熸垚鐨?Builder
            var person = new PersonBuilder()
                .WithName("John")
                .WithAge(30)
                .WithEmail("john@example.com")
                .Build();
            
            Console.WriteLine($"{person.Name}, {person.Age}, {person.Email}");
        }
    }
}


下一步

  1. 学习 增量管道和缓存
  2. 探索 最佳实践和FAQ

最后更新: 2025-01-21

基于 MIT 许可发布