你是否遇到过这样的困扰:明明写好了Source Generator,编译时却不按预期执行?或者多个Generator之间相互干扰,输出结果混乱?这些问题的根源往往在于对Source Generator底层执行机制的理解不足。
本文将带你深入C# Source Generator的内核,从编译器视角揭秘代码生成的完整流程。通过实战案例和关键节点分析,让你彻底掌握Generator的执行顺序和最佳实践,轻松避开常见陷阱。
在实际开发中,开发者经常遇到这些问题:
这些问题的核心在于缺乏对Source Generator执行机制的深度理解。
C# Source Generator并非独立运行,而是深度集成在Roslyn编译器的管道中:
text源码解析 → 语法分析 → 语义分析 → Generator执行 → 代码合并 → IL生成
关键执行节点:
Generator的生命周期包含以下关键步骤:
ISourceGenerator
:传统、较简单的接口(Initialize + Execute),适合小型生成器。IIncrementalGenerator
:增量式 API(推荐),能更高效地响应输入变更并缓存中间结果,减少不必要的重新生成。让我们从一个基础示例开始,观察Generator的执行顺序:
C#using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace SimpleOrderGenerator
{
[Generator]
public class SimpleOrderGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// 第一步:初始化阶段
Debug.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] Initialize 调用");
context.RegisterForSyntaxNotifications(() => new SimpleSyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
// 第二步:执行阶段
Debug.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] Execute 开始");
var syntaxReceiver = context.SyntaxReceiver as SimpleSyntaxReceiver;
// 生成简单的代码
string source = @"
using System;
namespace Generated
{
public static class TimeStamp
{
public static string GeneratedAt = """ + DateTime.Now + @""";
}
}";
context.AddSource("TimeStamp.g.cs", source);
Debug.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] Execute 完成");
}
}
public class SimpleSyntaxReceiver : ISyntaxReceiver
{
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// 语法节点访问时调用
if (syntaxNode is ClassDeclarationSyntax)
{
Debug.WriteLine($"发现类: {((ClassDeclarationSyntax)syntaxNode).Identifier}");
}
}
}
}
为了深入理解执行过程,我们来创建一个完整的监控Generator:
C#using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace SimpleOrderGenerator
{
[Generator]
public class SimpleOrderGenerator : ISourceGenerator
{
private static readonly List<string> ExecutionLog = new();
public void Initialize(GeneratorInitializationContext context)
{
LogExecution("Initialize - 开始");
// 注册语法通知
context.RegisterForSyntaxNotifications(() =>
{
LogExecution("SyntaxReceiver - 创建");
return new OrderTrackingSyntaxReceiver();
});
// 注册后续操作
context.RegisterForPostInitialization(ctx =>
{
LogExecution("PostInitialization - 执行");
GenerateExecutionReport(ctx);
});
LogExecution("Initialize - 完成");
}
public void Execute(GeneratorExecutionContext context)
{
LogExecution("Execute - 开始");
var receiver = context.SyntaxReceiver as OrderTrackingSyntaxReceiver;
LogExecution($"Execute - 发现 {receiver?.ClassCount} 个类");
// 生成主要代码
GenerateMainCode(context);
LogExecution("Execute - 完成");
}
private void GenerateMainCode(GeneratorExecutionContext context)
{
var sourceCode = $@"
using System;
using System.Collections.Generic;
namespace ExecutionOrder
{{
public static class GeneratorExecutionInfo
{{
public static List<string> ExecutionSteps = new List<string>
{{
{string.Join(",\n ", ExecutionLog.Select(log => $@"""{log}"""))}
}};
public static void PrintExecutionOrder()
{{
Console.WriteLine(""=== Generator 执行顺序 ==="");
for (int i = 0; i < ExecutionSteps.Count; i++)
{{
Console.WriteLine($""{{i + 1}}. {{ExecutionSteps[i]}}"");
}}
}}
}}
}}";
context.AddSource("ExecutionOrder.g.cs", sourceCode);
}
private void GenerateExecutionReport(GeneratorPostInitializationContext context)
{
var reportCode = @"
using System;
namespace ExecutionOrder
{
[System.AttributeUsage(System.AttributeTargets.Class)]
public class GeneratedInfoAttribute : System.Attribute
{
public string GeneratedAt { get; }
public GeneratedInfoAttribute(string generatedAt)
{
GeneratedAt = generatedAt;
}
}
}";
context.AddSource("GeneratedInfoAttribute.g.cs", reportCode);
}
private static void LogExecution(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
var logEntry = $"[{timestamp}] {message}";
ExecutionLog.Add(logEntry);
Debug.WriteLine(logEntry);
}
}
}
public class OrderTrackingSyntaxReceiver : ISyntaxReceiver
{
public int ClassCount { get; private set; }
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDecl)
{
ClassCount++;
Debug.WriteLine($"[Receiver] 访问类: {classDecl.Identifier.ValueText}");
}
}
}
项目配置文件
XML<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" PrivateAssets="all" />
</ItemGroup>
</Project>
通过深入剖析C# Source Generator的底层机制,我们掌握了三个关键要点:
掌握这些底层逻辑,不仅能让你写出更稳定的Source Generator,还能在遇到复杂场景时游刃有余。你在使用Source Generator时遇到过哪些执行顺序相关的问题?欢迎在评论区分享你的实战经验!
觉得这篇技术深度解析对你有帮助?请转发给更多C#同行,让大家一起提升Generator开发水平! 🚀
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!