API 快速入门指南
5 分钟快速上手 Roslyn Source Generator APIs
🎯 5 分钟快速开始
欢迎!本指南将帮助你快速理解和使用最常见的 Source Generator APIs。每个示例都是独立完整的,可以直接使用。
1. 生成简单代码
最直接的代码生成方式是使用 AddSource() 进行基于字符串的生成。
csharp
using Microsoft.CodeAnalysis;
[Generator]
public class SimpleGenerator : ISourceGenerator
{
public void Execute(GeneratorExecutionContext context)
{
// 生成一个简单的类
var sourceCode = @"
namespace Generated
{
public class HelloWorld
{
public string GetMessage() => ""Hello from generator!"";
}
}";
// 将生成的代码添加到编译中
context.AddSource("HelloWorld.g.cs", sourceCode);
}
public void Initialize(GeneratorInitializationContext context) { }
}关键要点:
- 使用
context.AddSource()添加生成的代码 - 第一个参数是文件名(必须以
.g.cs结尾) - 第二个参数是源代码字符串
2. 查找带有特定特性的类
使用 ForAttributeWithMetadataName() 进行高效的基于特性的代码生成(仅限增量生成器)。
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
[Generator]
public class AttributeGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 查找所有带有 [GenerateDto] 特性的类
var classDeclarations = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateDtoAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.Where(info => info is not null);
// 为每个类生成代码
context.RegisterSourceOutput(classDeclarations,
(spc, classInfo) => GenerateDto(spc, classInfo));
}
private static ClassInfo? GetClassInfo(GeneratorAttributeSyntaxContext context)
{
var classDecl = (ClassDeclarationSyntax)context.TargetNode;
return new ClassInfo(classDecl.Identifier.Text);
}
}
record ClassInfo(string Name);关键要点:
ForAttributeWithMetadataName()是查找带特性类型的最快方式- 使用
predicate提前过滤语法节点 - 使用
transform提取所需信息
3. 报告诊断信息
报告错误、警告或信息消息,帮助用户修复问题。
csharp
using Microsoft.CodeAnalysis;
public class DiagnosticGenerator : ISourceGenerator
{
// 定义诊断描述符
private static readonly DiagnosticDescriptor MustBePartialRule = new(
id: "GEN001",
title: "类必须是 partial",
messageFormat: "类 '{0}' 必须声明为 partial 才能使用代码生成",
category: "Generator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
public void Execute(GeneratorExecutionContext context)
{
// 检查类是否为 partial
bool isPartial = CheckIfPartial(context);
if (!isPartial)
{
// 报告错误
var diagnostic = Diagnostic.Create(
MustBePartialRule,
Location.None,
"MyClass");
context.ReportDiagnostic(diagnostic);
}
}
private bool CheckIfPartial(GeneratorExecutionContext context) => true;
public void Initialize(GeneratorInitializationContext context) { }
}关键要点:
- 使用唯一 ID 和消息创建
DiagnosticDescriptor - 使用
Diagnostic.Create()创建诊断实例 - 调用
context.ReportDiagnostic()报告到 IDE
4. 使用语义模型获取类型信息
使用语义模型访问类型信息、符号和语义分析。
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Linq;
public class SemanticAnalysisExample
{
public void AnalyzeClass(GeneratorExecutionContext context)
{
var compilation = context.Compilation;
var syntaxTree = compilation.SyntaxTrees.First();
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var root = syntaxTree.GetRoot();
var classDecl = root.DescendantNodes()
.OfType<ClassDeclarationSyntax>()
.First();
// 获取类的符号
var classSymbol = semanticModel.GetDeclaredSymbol(classDecl)
as INamedTypeSymbol;
if (classSymbol != null)
{
// 获取所有属性
var properties = classSymbol.GetMembers()
.OfType<IPropertySymbol>()
.Where(p => p.DeclaredAccessibility == Accessibility.Public);
// 使用属性信息进行生成
foreach (var prop in properties)
{
var propName = prop.Name;
var propType = prop.Type.ToDisplayString();
// 基于属性生成代码...
}
}
}
}关键要点:
- 使用
compilation.GetSemanticModel()获取语义信息 - 使用
GetDeclaredSymbol()从语法节点获取符号 - 通过符号访问成员、类型和可访问性
5. 增量生成以提高性能
使用增量生成器获得更好的性能和缓存。
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Linq;
[Generator]
public class IncrementalGeneratorExample : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 步骤 1:查找所有类声明
var classDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => (ClassDeclarationSyntax)ctx.Node)
.Where(c => c is not null);
// 步骤 2:收集所有类
var compilationAndClasses = context.CompilationProvider
.Combine(classDeclarations.Collect());
// 步骤 3:生成代码
context.RegisterSourceOutput(compilationAndClasses,
(spc, source) => GenerateCode(spc, source.Left, source.Right));
}
private void GenerateCode(
SourceProductionContext context,
Compilation compilation,
ImmutableArray<ClassDeclarationSyntax> classes)
{
foreach (var classDecl in classes)
{
var className = classDecl.Identifier.Text;
var code = $"// 为 {className} 生成";
context.AddSource($"{className}.g.cs", code);
}
}
}关键要点:
- 增量生成器缓存中间结果
- 使用
CreateSyntaxProvider()进行自定义过滤 - 使用
Combine()合并数据源 - 使用
RegisterSourceOutput()进行最终代码生成
📋 快速参考表
| 我想要... | 使用此 API | 示例 |
|---|---|---|
| 生成代码 | context.AddSource() | 示例 1 |
| 查找带特性的类 | ForAttributeWithMetadataName() | 示例 2 |
| 获取类型信息 | SemanticModel.GetDeclaredSymbol() | 示例 4 |
| 报告错误/警告 | context.ReportDiagnostic() | 示例 3 |
| 创建语法节点 | SyntaxFactory 方法 | 代码生成基础 |
| 查找所有类 | Compilation.GetSymbolsWithName() | 任务索引 |
| 获取类属性 | classSymbol.GetMembers().OfType<IPropertySymbol>() | 示例 4 |
| 优化性能 | 使用 IIncrementalGenerator | 示例 5 |
| 访问编译 | context.Compilation | 编译 API |
| 解析源代码 | CSharpSyntaxTree.ParseText() | 代码生成 API |
🗺️ 学习路径
初学者(刚开始)
如果你是 Roslyn Source Generators 的新手,请遵循以下路径:
从学习路径开始
阅读基础 API 文档
尝试简单示例
- 使用任务索引查找常见任务的解决方案
- 尝试本快速入门指南中的示例
预计时间: 2-3 小时
中级用户(已完成基础示例)
你已经构建了一个简单的生成器,想要学习更多:
学习高级概念
探索任务索引
- 浏览任务索引查找特定问题的解决方案
- 研究同一问题的不同方法
练习实际场景
- 构建 DTO 生成器
- 创建序列化生成器
- 实现构建器模式生成器
预计时间: 5-10 小时
高级用户(需要深入理解)
你正在构建复杂的生成器,需要全面的知识:
学习完整参考
掌握高级主题
学习最佳实践
预计时间: 20+ 小时
💡 成功提示
从简单开始
- 从基于字符串的代码生成开始
- 从一开始就使用增量生成器以获得更好的性能
- 使用小而专注的示例进行测试
使用正确的工具
- 查找带特性的类型: 使用
ForAttributeWithMetadataName() - 获取类型信息: 使用
SemanticModel - 代码生成: 从字符串开始,需要时转向
SyntaxFactory - 性能: 始终使用
IIncrementalGenerator
避免常见陷阱
- ❌ 不要在新项目中使用
ISourceGenerator(改用IIncrementalGenerator) - ❌ 不要在没有适当错误处理的情况下生成代码
- ❌ 不要忘记将目标类设为
partial - ❌ 不要忽略诊断消息
获取帮助
🔗 下一步
现在你已经了解了基础知识,接下来该做什么:
准备好开始了吗? 选择上面的一个示例,在你的项目中尝试一下!