Hello World 示例
项目位置
01-HelloWorld/
学习目标
- 创建最简单的源生成器
- 理解
ISourceGenerator接口 - 学习如何添加生成的源文件
项目结构
01-HelloWorld/
├── HelloWorld.Generator/ # 生成器项目
│ └── HelloWorldGenerator.cs
└── HelloWorld.Consumer/ # 使用生成器的项目
└── Program.cs生成器代码
using Microsoft.CodeAnalysis;
namespace HelloWorld.Generator
{
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
// 初始化代码(本示例中为空)
}
public void Execute(GeneratorExecutionContext context)
{
// 生成的源代码
var sourceCode = @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello from Source Generator!"";
}
}";
// 添加源文件到编译
context.AddSource("HelloWorld.g.cs", sourceCode);
}
}
}使用生成的代码
using System;
using Generated;
class Program
{
static void Main()
{
// 调用生成器生成的方法
Console.WriteLine(HelloWorld.SayHello());
// 输出: Hello from Source Generator!
}
}运行示例
cd 01-HelloWorld
dotnet build
dotnet run --project HelloWorld.Consumer关键概念
1. [Generator] 特性
标记类为源生成器:
[Generator]
public class HelloWorldGenerator : ISourceGenerator2. ISourceGenerator 接口
必须实现两个方法:
Initialize- 初始化生成器Execute- 执行代码生成
3. AddSource 方法
添加生成的源文件:
context.AddSource(
"HelloWorld.g.cs", // 文件名(必须唯一)
sourceCode // 源代码内容
);生成的文件
编译后,可以在 obj/ 目录下找到生成的文件:
obj/Debug/net8.0/generated/
└── HelloWorld.Generator/
└── HelloWorld.Generator.HelloWorldGenerator/
└── HelloWorld.g.cs常见问题
Q: 为什么看不到生成的代码?
A: 生成的代码在 obj/ 目录下,IDE 可能不会自动显示。可以:
- 在 Solution Explorer 中启用"显示所有文件"
- 使用
dotnet build -v detailed查看详细输出
Q: 如何调试生成器?
A: 参考 调试和诊断 文档。
下一步
- 增量生成器示例 - 学习性能优化
- ToString 生成器 - 学习实用生成器
- 查看 API 参考
项目结构详解
完整目录结构
01-HelloWorld/
├── HelloWorld.slnx # 解决方案文件
├── HelloWorld.Generator/ # 生成器项目
│ ├── HelloWorld.Generator.csproj # 项目文件
│ └── HelloWorldGenerator.cs # 生成器实现
└── HelloWorld.Consumer/ # 消费者项目
├── HelloWorld.Consumer.csproj # 项目文件
└── Program.cs # 主程序项目结构图
完整项目配置
生成器项目配置
HelloWorld.Generator.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 目标框架:必须是 .NET Standard 2.0 -->
<TargetFramework>netstandard2.0</TargetFramework>
<!-- 语言版本 -->
<LangVersion>latest</LangVersion>
<!-- 不生成引用程序集 -->
<GenerateReferenceAssemblyAttribute>false</GenerateReferenceAssemblyAttribute>
<!-- 启用可空引用类型 -->
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- Roslyn 核心包 -->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
</ItemGroup>
</Project>关键配置说明:
TargetFramework: 必须是
netstandard2.0- 源生成器在编译时运行
- 必须兼容所有 .NET 版本
PrivateAssets="all":
- 防止 Roslyn 包传递到消费者项目
- 减少依赖冲突
GenerateReferenceAssemblyAttribute:
- 避免生成不必要的引用程序集
消费者项目配置
HelloWorld.Consumer.csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- 目标框架:可以是任何 .NET 版本 -->
<TargetFramework>net8.0</TargetFramework>
<OutputType>Exe</OutputType>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<!-- 引用生成器项目 -->
<ProjectReference Include="..\HelloWorld.Generator\HelloWorld.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- 可选:输出生成的文件到指定目录 -->
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<!-- 可选:将生成的文件包含到项目中 -->
<ItemGroup>
<Compile Remove="Generated/**/*.cs" />
</ItemGroup>
</Project>关键配置说明:
OutputItemType="Analyzer":
- 将生成器作为分析器引用
- 这是最重要的配置
ReferenceOutputAssembly="false":
- 不引用生成器的输出程序集
- 只使用生成器的代码生成功能
EmitCompilerGeneratedFiles:
- 将生成的文件输出到指定目录
- 方便查看和调试
生成器代码详解
完整生成器实现
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
namespace HelloWorld.Generator
{
/// <summary>
/// Hello World 源生成器
/// 这是最简单的源生成器示例,演示基本的代码生成流程
/// </summary>
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
/// <summary>
/// 初始化方法
/// 在生成器首次加载时调用一次
/// 可以在这里注册语法接收器或其他初始化逻辑
/// </summary>
/// <param name="context">初始化上下文</param>
public void Initialize(GeneratorInitializationContext context)
{
// 本示例中不需要初始化逻辑
// 在更复杂的生成器中,可以在这里:
// 1. 注册语法接收器:context.RegisterForSyntaxNotifications(...)
// 2. 注册后处理器:context.RegisterForPostInitialization(...)
#if DEBUG
// 调试时可以附加调试器
// System.Diagnostics.Debugger.Launch();
#endif
}
/// <summary>
/// 执行方法
/// 每次编译时都会调用
/// 在这里生成源代码并添加到编译中
/// </summary>
/// <param name="context">执行上下文</param>
public void Execute(GeneratorExecutionContext context)
{
// 1. 生成源代码
var sourceCode = GenerateHelloWorldClass();
// 2. 将源代码添加到编译中
// 参数1: 文件名(必须唯一,建议使用 .g.cs 后缀)
// 参数2: 源代码内容
context.AddSource(
hintName: "HelloWorld.g.cs",
sourceText: SourceText.From(sourceCode, Encoding.UTF8)
);
// 可以添加多个源文件
// context.AddSource("AnotherFile.g.cs", anotherSourceCode);
}
/// <summary>
/// 生成 HelloWorld 类的源代码
/// </summary>
/// <returns>生成的 C# 源代码</returns>
private string GenerateHelloWorldClass()
{
// 使用字符串插值生成代码
// 注意:实际项目中建议使用 StringBuilder 或代码生成库
return @"
// <auto-generated/>
// 此文件由 HelloWorldGenerator 自动生成
// 生成时间: " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + @"
namespace Generated
{
/// <summary>
/// 自动生成的 Hello World 类
/// </summary>
public static class HelloWorld
{
/// <summary>
/// 返回问候消息
/// </summary>
/// <returns>问候消息字符串</returns>
public static string SayHello()
{
return ""Hello from Source Generator!"";
}
/// <summary>
/// 返回带名字的问候消息
/// </summary>
/// <param name=""name"">名字</param>
/// <returns>个性化问候消息</returns>
public static string SayHello(string name)
{
return $""Hello {name} from Source Generator!"";
}
/// <summary>
/// 返回生成器信息
/// </summary>
/// <returns>生成器信息</returns>
public static string GetGeneratorInfo()
{
return ""Generated by HelloWorldGenerator at " + System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + @""";
}
}
}
";
}
}
}代码生成技巧
1. 使用 StringBuilder 生成复杂代码
private string GenerateComplexClass()
{
var sb = new StringBuilder();
// 添加文件头
sb.AppendLine("// <auto-generated/>");
sb.AppendLine();
// 添加命名空间
sb.AppendLine("namespace Generated");
sb.AppendLine("{");
// 添加类定义
sb.AppendLine(" public static class HelloWorld");
sb.AppendLine(" {");
// 添加方法
for (int i = 1; i <= 5; i++)
{
sb.AppendLine($" public static string Method{i}()");
sb.AppendLine(" {");
sb.AppendLine($" return \"Method {i}\";");
sb.AppendLine(" }");
sb.AppendLine();
}
// 关闭类和命名空间
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}2. 使用原始字符串字面量(C# 11+)
private string GenerateWithRawString()
{
return """
// <auto-generated/>
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => "Hello!";
}
}
""";
}3. 使用代码生成辅助类
public class CodeBuilder
{
private readonly StringBuilder _sb = new();
private int _indentLevel = 0;
private const string IndentString = " ";
public CodeBuilder AppendLine(string line = "")
{
if (!string.IsNullOrEmpty(line))
{
_sb.Append(IndentString, _indentLevel);
}
_sb.AppendLine(line);
return this;
}
public CodeBuilder OpenBrace()
{
AppendLine("{");
_indentLevel++;
return this;
}
public CodeBuilder CloseBrace()
{
_indentLevel--;
AppendLine("}");
return this;
}
public override string ToString() => _sb.ToString();
}
// 使用示例
private string GenerateWithBuilder()
{
var builder = new CodeBuilder();
builder
.AppendLine("namespace Generated")
.OpenBrace()
.AppendLine("public static class HelloWorld")
.OpenBrace()
.AppendLine("public static string SayHello() => \"Hello!\";")
.CloseBrace()
.CloseBrace();
return builder.ToString();
}执行流程图
编译时执行流程
运行时执行流程
关键技术点
1. [Generator] 特性
[Generator]
public class HelloWorldGenerator : ISourceGenerator
{
// ...
}作用:
- 标记类为源生成器
- 编译器会自动发现并加载
- 必须实现
ISourceGenerator接口
注意事项:
- 一个程序集可以包含多个生成器
- 每个生成器必须有唯一的类名
- 生成器类必须是 public
2. ISourceGenerator 接口
public interface ISourceGenerator
{
void Initialize(GeneratorInitializationContext context);
void Execute(GeneratorExecutionContext context);
}Initialize 方法:
- 生成器加载时调用一次
- 用于注册回调和初始化
- 不应该执行耗时操作
Execute 方法:
- 每次编译时调用
- 在这里生成代码
- 应该是幂等的(相同输入产生相同输出)
3. AddSource 方法
context.AddSource(
hintName: "HelloWorld.g.cs", // 文件名提示
sourceText: SourceText.From(code, Encoding.UTF8) // 源代码
);参数说明:
hintName: 生成文件的名称提示
- 必须唯一
- 建议使用
.g.cs后缀 - 不能包含路径分隔符
sourceText: 源代码内容
- 使用
SourceText.From()创建 - 指定编码(推荐 UTF-8)
- 必须是有效的 C# 代码
- 使用
最佳实践:
// ✅ 推荐:使用 SourceText.From
context.AddSource(
"HelloWorld.g.cs",
SourceText.From(code, Encoding.UTF8)
);
// ✅ 也可以:直接传递字符串(会自动转换)
context.AddSource("HelloWorld.g.cs", code);
// ❌ 避免:文件名包含路径
context.AddSource("Generated/HelloWorld.g.cs", code); // 错误!
// ❌ 避免:文件名重复
context.AddSource("HelloWorld.g.cs", code1);
context.AddSource("HelloWorld.g.cs", code2); // 错误!会覆盖4. 生成的代码规范
// ✅ 推荐的生成代码格式
var code = @"
// <auto-generated/>
#nullable enable
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello!"";
}
}
";规范说明:
添加
<auto-generated/>注释- 标记为自动生成的代码
- 某些工具会跳过这些文件
使用
#nullable enable- 启用可空引用类型
- 与现代 C# 项目保持一致
使用完整的命名空间
- 避免命名冲突
- 使代码更清晰
添加 XML 文档注释
- 提供 IntelliSense 支持
- 帮助用户理解生成的代码
代码生成原理
编译管道中的位置
生成器的执行时机
编译开始
- 编译器加载所有源生成器
- 调用每个生成器的
Initialize()方法
语法分析完成
- 编译器构建语法树
- 准备语义模型
生成器执行
- 调用每个生成器的
Execute()方法 - 生成器可以访问语法树和语义模型
- 生成器添加新的源文件
- 调用每个生成器的
重新编译
- 编译器将生成的代码加入编译
- 重新进行语义分析
- 生成最终的 IL 代码
生成器的限制
只能添加代码,不能修改
- 生成器只能添加新文件
- 不能修改现有代码
- 不能删除代码
必须是确定性的
- 相同输入必须产生相同输出
- 不应该依赖外部状态
- 不应该有副作用
性能要求
- 生成器在编译时运行
- 必须快速执行
- 避免耗时操作
最佳实践 vs 反模式
最佳实践
✅ 1. 使用有意义的文件名
// ✅ 好:清晰的文件名
context.AddSource("HelloWorld.g.cs", code);
context.AddSource("HelloWorld.Extensions.g.cs", extensionCode);
// ❌ 差:模糊的文件名
context.AddSource("Generated.cs", code);
context.AddSource("File1.cs", code);✅ 2. 添加文件头注释
var code = @"
// <auto-generated/>
// This file is generated by HelloWorldGenerator
// Do not modify this file manually
namespace Generated
{
// ...
}
";✅ 3. 使用 SourceText.From
// ✅ 好:指定编码
context.AddSource(
"HelloWorld.g.cs",
SourceText.From(code, Encoding.UTF8)
);
// ❌ 差:依赖默认编码
context.AddSource("HelloWorld.g.cs", code);✅ 4. 处理异常
public void Execute(GeneratorExecutionContext context)
{
try
{
var code = GenerateCode();
context.AddSource("HelloWorld.g.cs", code);
}
catch (Exception ex)
{
// 报告诊断信息而不是抛出异常
context.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(
"HW001",
"Generation Error",
$"Failed to generate code: {ex.Message}",
"HelloWorldGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true
),
Location.None
));
}
}反模式
❌ 1. 在 Initialize 中生成代码
// ❌ 错误:不要在 Initialize 中生成代码
public void Initialize(GeneratorInitializationContext context)
{
// 错误!Initialize 只用于初始化
var code = GenerateCode();
// context.AddSource(...); // 这里没有 AddSource 方法
}
// ✅ 正确:在 Execute 中生成代码
public void Execute(GeneratorExecutionContext context)
{
var code = GenerateCode();
context.AddSource("HelloWorld.g.cs", code);
}❌ 2. 依赖外部状态
// ❌ 错误:依赖外部文件
public void Execute(GeneratorExecutionContext context)
{
var template = File.ReadAllText("template.txt"); // 错误!
var code = ProcessTemplate(template);
context.AddSource("HelloWorld.g.cs", code);
}
// ✅ 正确:将模板嵌入到生成器中
private const string Template = @"
namespace Generated
{
public static class HelloWorld { }
}
";
public void Execute(GeneratorExecutionContext context)
{
var code = ProcessTemplate(Template);
context.AddSource("HelloWorld.g.cs", code);
}❌ 3. 生成无效的 C# 代码
// ❌ 错误:生成的代码有语法错误
var code = @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() // 缺少方法体
}
}
";
// ✅ 正确:确保生成的代码是有效的
var code = @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello()
{
return ""Hello!"";
}
}
}
";扩展练习
练习 1: 添加参数
目标: 修改生成器,生成带参数的方法
提示:
public static string SayHello(string name)
{
return $"Hello {name} from Source Generator!";
}练习 2: 生成多个方法
目标: 生成多个不同的问候方法
提示:
public static string SayGoodMorning() => "Good morning!";
public static string SayGoodNight() => "Good night!";练习 3: 添加时间戳
目标: 在生成的代码中包含生成时间
提示:
public static string GetGenerationTime()
{
return "Generated at: 2025-01-21 10:30:00";
}练习 4: 生成多个文件
目标: 生成多个不同的类文件
提示:
context.AddSource("HelloWorld.g.cs", helloWorldCode);
context.AddSource("Goodbye.g.cs", goodbyeCode);练习 5: 添加 XML 文档注释
目标: 为生成的方法添加 XML 文档注释
提示:
/// <summary>
/// 返回问候消息
/// </summary>
/// <returns>问候消息字符串</returns>
public static string SayHello() => "Hello!";常见问题
Q1: 为什么我的生成器没有运行?
A: 检查以下几点:
- 项目引用配置
<ProjectReference Include="..\HelloWorld.Generator\HelloWorld.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />- 生成器项目目标框架
<TargetFramework>netstandard2.0</TargetFramework>- [Generator] 特性
[Generator]
public class HelloWorldGenerator : ISourceGenerator- 清理并重新构建
dotnet clean
dotnet buildQ2: 如何查看生成的代码?
A: 三种方法:
- 在 obj 目录中查看
obj/Debug/net8.0/generated/HelloWorld.Generator/HelloWorld.Generator.HelloWorldGenerator/HelloWorld.g.cs- 配置输出到指定目录
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>- 在 Visual Studio 中查看
- 展开项目节点
- 展开 "Dependencies" → "Analyzers" → "HelloWorld.Generator"
- 查看生成的文件
Q3: 生成的代码报错怎么办?
A: 调试步骤:
检查生成的代码语法
- 确保代码是有效的 C#
- 检查括号、分号等
添加调试输出
public void Execute(GeneratorExecutionContext context)
{
var code = GenerateCode();
// 输出到构建日志
System.Diagnostics.Debug.WriteLine(code);
context.AddSource("HelloWorld.g.cs", code);
}- 使用 Debugger.Launch()
public void Execute(GeneratorExecutionContext context)
{
#if DEBUG
System.Diagnostics.Debugger.Launch();
#endif
// ...
}Q4: 如何在生成器中使用第三方库?
A: 注意事项:
- 只能使用 .NET Standard 2.0 兼容的库
- 避免大型依赖
- 考虑将代码内联
<ItemGroup>
<!-- 可以使用的库 -->
<PackageReference Include="System.Text.Json" Version="6.0.0" PrivateAssets="all" />
<!-- 避免使用运行时库 -->
<!-- <PackageReference Include="EntityFrameworkCore" /> ❌ -->
</ItemGroup>Q5: 生成器性能很慢怎么办?
A: 优化建议:
- 使用增量生成器(参见下一个示例)
- 缓存计算结果
- 避免重复操作
- 减少生成的代码量
Q6: 如何测试生成器?
A: 参考 测试调试示例
🔗 相关资源
深入学习
- 快速开始 - 开始使用源生成器
- 增量生成器示例 - 学习增量生成器的使用
- ToString 生成器 - 实用的代码生成示例
- Builder 生成器 - 复杂的代码生成模式
- 测试示例 - 了解如何测试源生成器
API 参考
官方文档
学习指南
下一步
- 增量生成器示例 - 学习性能优化
- ToString 生成器 - 学习实用生成器
- 诊断报告示例 - 学习错误处理
API 参考
最后更新: 2025-01-21
🔍 深入理解生成器工作原理
编译器集成
源生成器是编译器的一部分,在编译过程中运行。理解这一点对于编写高效的生成器至关重要。
编译器如何发现生成器
- 程序集扫描:编译器扫描所有引用的程序集
- 特性识别:查找标记了
[Generator]特性的类 - 接口验证:确认类实现了
ISourceGenerator接口 - 实例化:创建生成器实例并调用方法
生成器的生命周期
生成的代码如何被使用
生成的代码会被添加到编译中,就像它是源文件的一部分一样:
// 原始代码 (Program.cs)
using Generated;
class Program
{
static void Main()
{
// 调用生成的代码
Console.WriteLine(HelloWorld.SayHello());
}
}
// 生成的代码 (HelloWorld.g.cs)
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => "Hello from Source Generator!";
}
}
// 编译器将两者合并编译🎨 代码生成的艺术
字符串构建技巧
技巧 1: 使用原始字符串字面量
C# 11 引入的原始字符串字面量让代码生成更简单:
// ✅ 使用原始字符串字面量(C# 11+)
var code = """
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => "Hello!";
}
}
""";
// ❌ 传统方式(需要转义)
var oldCode = @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello!"";
}
}";技巧 2: 使用插值字符串
var className = "HelloWorld";
var methodName = "SayHello";
var message = "Hello from Generator!";
var code = $$"""
namespace Generated
{
public static class {{className}}
{
public static string {{methodName}}() => "{{message}}";
}
}
""";技巧 3: 使用 StringBuilder 处理复杂逻辑
public string GenerateMultipleMethods(int count)
{
var sb = new StringBuilder();
sb.AppendLine("namespace Generated");
sb.AppendLine("{");
sb.AppendLine(" public static class HelloWorld");
sb.AppendLine(" {");
for (int i = 1; i <= count; i++)
{
sb.AppendLine($" public static string Method{i}()");
sb.AppendLine(" {");
sb.AppendLine($" return \"Method {i}\";");
sb.AppendLine(" }");
if (i < count)
{
sb.AppendLine();
}
}
sb.AppendLine(" }");
sb.AppendLine("}");
return sb.ToString();
}代码格式化
保持一致的缩进
public class IndentedCodeBuilder
{
private readonly StringBuilder _sb = new();
private int _indentLevel = 0;
private const string IndentString = " "; // 4 个空格
public IndentedCodeBuilder AppendLine(string line = "")
{
if (!string.IsNullOrEmpty(line))
{
for (int i = 0; i < _indentLevel; i++)
{
_sb.Append(IndentString);
}
}
_sb.AppendLine(line);
return this;
}
public IndentedCodeBuilder Indent()
{
_indentLevel++;
return this;
}
public IndentedCodeBuilder Unindent()
{
if (_indentLevel > 0)
{
_indentLevel--;
}
return this;
}
public override string ToString() => _sb.ToString();
}
// 使用示例
var builder = new IndentedCodeBuilder();
builder
.AppendLine("namespace Generated")
.AppendLine("{")
.Indent()
.AppendLine("public static class HelloWorld")
.AppendLine("{")
.Indent()
.AppendLine("public static string SayHello() => \"Hello!\";")
.Unindent()
.AppendLine("}")
.Unindent()
.AppendLine("}");
var code = builder.ToString();🚀 进阶练习
练习 6: 生成多个类
目标: 修改生成器,生成多个不同的类
提示:
public void Execute(GeneratorExecutionContext context)
{
// 生成 HelloWorld 类
context.AddSource("HelloWorld.g.cs", GenerateHelloWorld());
// 生成 Goodbye 类
context.AddSource("Goodbye.g.cs", GenerateGoodbye());
// 生成 Greeting 类
context.AddSource("Greeting.g.cs", GenerateGreeting());
}
private string GenerateHelloWorld()
{
return @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello!"";
}
}";
}
private string GenerateGoodbye()
{
return @"
namespace Generated
{
public static class Goodbye
{
public static string SayGoodbye() => ""Goodbye!"";
}
}";
}
private string GenerateGreeting()
{
return @"
namespace Generated
{
public static class Greeting
{
public static string Greet(string name) => $""Hello, {name}!"";
}
}";
}练习 7: 添加条件编译
目标: 根据编译配置生成不同的代码
提示:
public void Execute(GeneratorExecutionContext context)
{
// 检查是否是 Debug 模式
var isDebug = context.ParseOptions
.PreprocessorSymbolNames
.Contains("DEBUG");
var code = isDebug
? GenerateDebugCode()
: GenerateReleaseCode();
context.AddSource("HelloWorld.g.cs", code);
}
private string GenerateDebugCode()
{
return @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello from Debug!"";
public static void DebugInfo()
{
System.Console.WriteLine(""Debug mode enabled"");
}
}
}";
}
private string GenerateReleaseCode()
{
return @"
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello from Release!"";
}
}";
}练习 8: 生成扩展方法
目标: 生成字符串扩展方法
提示:
private string GenerateExtensionMethods()
{
return @"
namespace Generated
{
public static class StringExtensions
{
public static string Reverse(this string str)
{
var chars = str.ToCharArray();
System.Array.Reverse(chars);
return new string(chars);
}
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
public static string Repeat(this string str, int count)
{
return string.Concat(System.Linq.Enumerable.Repeat(str, count));
}
}
}";
}练习 9: 生成接口实现
目标: 自动生成接口的默认实现
提示:
private string GenerateInterfaceImplementation()
{
return @"
namespace Generated
{
public interface IGreeter
{
string Greet(string name);
}
public class DefaultGreeter : IGreeter
{
public string Greet(string name)
{
return $""Hello, {name}!"";
}
}
public class FormalGreeter : IGreeter
{
public string Greet(string name)
{
return $""Good day, {name}."";
}
}
}";
}练习 10: 生成单元测试辅助类
目标: 生成测试辅助代码
提示:
private string GenerateTestHelpers()
{
return @"
namespace Generated.Testing
{
public static class TestHelpers
{
public static void AssertHelloWorld(string actual)
{
const string expected = ""Hello from Source Generator!"";
if (actual != expected)
{
throw new System.Exception(
$""Expected: {expected}, Actual: {actual}"");
}
}
public static void RunAllTests()
{
System.Console.WriteLine(""Running tests..."");
var result = HelloWorld.SayHello();
AssertHelloWorld(result);
System.Console.WriteLine(""All tests passed!"");
}
}
}";
}🎓 学习路径建议
初学者路径
第 1 周:理解 Hello World 示例
- 创建基本的生成器
- 理解
ISourceGenerator接口 - 学习如何添加源文件
第 2 周:学习代码生成技巧
- 使用 StringBuilder
- 处理字符串转义
- 格式化生成的代码
第 3 周:探索更多示例
中级路径
第 4-5 周:学习语法分析
- 理解语法树
- 使用 SyntaxReceiver
- 访问语义模型
第 6-7 周:实现实用生成器
第 8 周:学习测试和调试
- 测试示例
- 调试技巧
高级路径
📚 补充资源
官方文档
社区资源
相关工具
- SharpLab - 在线查看生成的代码
- RoslynQuoter - 生成 Roslyn API 代码
- Roslyn Syntax Visualizer - 可视化语法树
🎯 实战项目建议
项目 1: 配置类生成器
创建一个生成器,自动为配置类生成验证代码:
// 输入
[GenerateValidation]
public class AppConfig
{
[Required]
public string ConnectionString { get; set; }
[Range(1, 100)]
public int MaxConnections { get; set; }
}
// 生成的代码
public partial class AppConfig
{
public List<string> Validate()
{
var errors = new List<string>();
if (string.IsNullOrEmpty(ConnectionString))
errors.Add("ConnectionString is required");
if (MaxConnections < 1 || MaxConnections > 100)
errors.Add("MaxConnections must be between 1 and 100");
return errors;
}
}项目 2: DTO 映射生成器
自动生成 DTO 和实体之间的映射代码:
// 输入
[GenerateMapper]
public class UserDto
{
public string Name { get; set; }
public string Email { get; set; }
}
public class User
{
public string Name { get; set; }
public string Email { get; set; }
public DateTime CreatedAt { get; set; }
}
// 生成的代码
public static class UserMapper
{
public static UserDto ToDto(User user)
{
return new UserDto
{
Name = user.Name,
Email = user.Email
};
}
public static User ToEntity(UserDto dto)
{
return new User
{
Name = dto.Name,
Email = dto.Email,
CreatedAt = DateTime.Now
};
}
}项目 3: 枚举扩展生成器
为枚举类型生成扩展方法:
// 输入
[GenerateExtensions]
public enum Status
{
[Description("待处理")]
Pending,
[Description("进行中")]
InProgress,
[Description("已完成")]
Completed
}
// 生成的代码
public static class StatusExtensions
{
public static string GetDescription(this Status status)
{
return status switch
{
Status.Pending => "待处理",
Status.InProgress => "进行中",
Status.Completed => "已完成",
_ => status.ToString()
};
}
public static Status Parse(string value)
{
return value switch
{
"待处理" => Status.Pending,
"进行中" => Status.InProgress,
"已完成" => Status.Completed,
_ => throw new ArgumentException($"Invalid status: {value}")
};
}
}💡 专家提示
提示 1: 使用 #nullable enable
在生成的代码中启用可空引用类型:
var code = @"
// <auto-generated/>
#nullable enable
namespace Generated
{
public static class HelloWorld
{
public static string? SayHello(string? name)
{
return name != null ? $""Hello, {name}!"" : null;
}
}
}
";提示 2: 添加 EditorBrowsable 特性
隐藏生成的代码,避免 IntelliSense 混乱:
var code = @"
using System.ComponentModel;
namespace Generated
{
[EditorBrowsable(EditorBrowsableState.Never)]
public static class HelloWorld
{
public static string SayHello() => ""Hello!"";
}
}
";提示 3: 使用 #pragma warning disable
禁用生成代码的警告:
var code = @"
// <auto-generated/>
#pragma warning disable CS1591 // Missing XML comment
#pragma warning disable CS8019 // Unnecessary using directive
namespace Generated
{
public static class HelloWorld
{
public static string SayHello() => ""Hello!"";
}
}
#pragma warning restore CS8019
#pragma warning restore CS1591
";提示 4: 添加生成器版本信息
在生成的代码中包含版本信息:
public void Execute(GeneratorExecutionContext context)
{
var version = GetType().Assembly.GetName().Version;
var code = $@"
// <auto-generated/>
// Generator: HelloWorldGenerator
// Version: {version}
// Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}
namespace Generated
{{
public static class HelloWorld
{{
public static string SayHello() => ""Hello from Source Generator!"";
public static string GetGeneratorVersion() => ""{version}"";
}}
}}
";
context.AddSource("HelloWorld.g.cs", code);
}🎉 总结
恭喜你完成了 Hello World 示例的学习!你现在应该:
✅ 理解源生成器的基本概念 ✅ 知道如何创建简单的生成器 ✅ 掌握代码生成的基本技巧 ✅ 了解如何调试和测试生成器 ✅ 准备好学习更高级的主题
下一步行动
- 实践: 尝试完成所有扩展练习
- 探索: 查看其他示例项目
- 构建: 创建自己的生成器
- 分享: 与社区分享你的经验
记住,源生成器是一个强大的工具,但也需要谨慎使用。始终考虑性能、可维护性和用户体验。
祝你在源生成器的旅程中取得成功!🚀
最后更新: 2025-01-21