增量生成器基础概念和示例
本文档介绍增量生成器的基础概念、为什么需要它以及基本使用方法。
📋 文档信息
- 难度级别: 中级
- 预计阅读时间: 20 分钟
澧為噺鐢熸垚鍣ㄧず渚?
椤圭洰浣嶇疆
03-IncrementalGenerator/
瀛︿範鐩爣
- 浣跨敤
IIncrementalGenerator鎺ュ彛 - 鐞嗚В澧為噺绠¢亾鎿嶄綔
- 瀛︿範鎬ц兘浼樺寲鎶€宸?
涓轰粈涔堥渶瑕佸閲忕敓鎴愬櫒锛?
浼犵粺鐨?ISourceGenerator 鍦ㄦ瘡娆$紪璇戞椂閮戒細閲嶆柊鎵ц锛屽嵆浣夸唬鐮佹病鏈夊彉鍖栥€傚閲忕敓鎴愬櫒鍙互锛?
- 鉁?缂撳瓨鏈彉鍖栫殑缁撴灉
- 鉁?鍙鐞嗗彉鍖栫殑閮ㄥ垎
- 鉁?鏄捐憲鎻愰珮缂栬瘧閫熷害
鎬ц兘瀵规瘮
| 鍦烘櫙 | ISourceGenerator | IIncrementalGenerator |
|---|---|---|
| 棣栨缂栬瘧 | 1000ms | 1000ms |
| 鏃犲彉鍖栭噸缂栬瘧 | 1000ms | 10ms 鈿? |
| 灏忔敼鍔ㄩ噸缂栬瘧 | 1000ms | 100ms 鈿? |
鐢熸垚鍣ㄤ唬鐮?
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}");
}
}
}下一步
最后更新: 2025-01-21