故障排除
系统化的故障排除指南,快速解决常见问题
📋 文档信息
难度级别: 中级
预计阅读时间: 20 分钟
前置知识:
- 源生成器基础
- 项目配置基础
- 编译过程理解
🎯 学习目标
学完本章节后,你将能够:
- 识别和解决常见错误
- 使用系统化的排查方法
- 快速查找诊断命令
- 参考 FAQ 解决问题
- 使用调试清单
🐛 常见错误详解
错误 1: 生成器不执行
症状:
- 编译成功,但没有生成代码
- 没有任何错误或警告
原因:
- 生成器项目配置不正确
- 没有正确引用生成器
- 特性未找到
解决方案:
xml
<!-- 检查项目引用配置 -->
<ItemGroup>
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- 检查生成器项目配置 -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>错误 2: 生成的代码不可见
症状:
- 生成器执行了
- 但生成的代码在 IDE 中不可见
原因:
- 没有使用 partial 类
- 命名空间不匹配
- 文件名冲突
解决方案:
确保类声明为 partial:
csharp
public partial class MyClass // 必须是 partial
{
}检查命名空间匹配:
csharp
// 生成的代码
namespace MyNamespace // 必须与原始类的命名空间匹配
{
public partial class MyClass
{
// 生成的成员
}
}错误 3: 命名冲突
症状:
error CS0101: The namespace 'MyNamespace' already contains a definition for 'MyClass'原因:
- 生成的类名与现有类名冲突
- 多个生成器生成了相同的类
解决方案:
csharp
// 方案 1: 使用唯一的文件名
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
var fileName = $"{classInfo.Name}_{GetType().Name}.g.cs";
spc.AddSource(fileName, code);
});
// 方案 2: 使用嵌套类
private string GenerateCode(ClassInfo classInfo)
{
return $@"
namespace {classInfo.Namespace}
{{
public partial class {classInfo.Name}
{{
// 生成的代码放在嵌套类中
public static class Generated
{{
// ...
}}
}}
}}";
}
// 方案 3: 使用不同的命名空间
private string GenerateCode(ClassInfo classInfo)
{
return $@"
namespace {classInfo.Namespace}.Generated
{{
public class {classInfo.Name}Extensions
{{
// ...
}}
}}";
}错误 4: 循环依赖
症状:
error CS0146: Circular base class dependency原因:
- 生成的代码创建了循环继承
- 生成的代码引用了尚未生成的类型
解决方案:
csharp
// ❌ 错误:创建循环依赖
public partial class A : B { }
public partial class B : A { }
// ✅ 正确:使用接口打破循环
public interface IA { }
public interface IB { }
public partial class A : IA
{
public IB BProperty { get; set; }
}
public partial class B : IB
{
public IA AProperty { get; set; }
}错误 5: 生成的代码语法错误
症状:
error CS1002: ; expected
error CS1513: } expected原因:
- 生成的代码有语法错误
- 字符串插值或转义问题
调试方法:
csharp
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
var code = GenerateCode(classInfo);
#if DEBUG
// 验证生成的代码是否可以解析
var syntaxTree = CSharpSyntaxTree.ParseText(code);
var diagnostics = syntaxTree.GetDiagnostics();
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
{
// 输出语法错误
foreach (var diagnostic in diagnostics)
{
System.Diagnostics.Debug.WriteLine($"Syntax Error: {diagnostic}");
}
// 输出生成的代码
System.Diagnostics.Debug.WriteLine("Generated Code:");
System.Diagnostics.Debug.WriteLine(code);
}
#endif
spc.AddSource($"{classInfo.Name}.g.cs", code);
});常见语法错误:
csharp
// ❌ 错误:字符串中的引号未转义
var code = $"public string Name => "{classInfo.Name}";";
// ✅ 正确:转义引号
var code = $"public string Name => \"{classInfo.Name}\";";
// ❌ 错误:多行字符串格式不正确
var code = $"public class {classInfo.Name}
{
}";
// ✅ 正确:使用 @"" 或正确的换行
var code = $@"
public class {classInfo.Name}
{{
}}";错误 6: 特性未找到
症状:
- 生成器不执行
- 没有生成任何代码
原因:
- 特性类不在正确的位置
- 特性命名空间不匹配
- 特性未标记为 public
解决方案:
csharp
// 确保特性是 public 的
[AttributeUsage(AttributeTargets.Class)]
public class GenerateAttribute : Attribute
{
}
// 在生成器中使用完全限定名称
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"MyNamespace.GenerateAttribute", // 使用完全限定名称
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));
// 或者只使用类名(不包含 Attribute 后缀)
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"Generate", // 不包含 Attribute 后缀
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx));错误 7: 内存泄漏
症状:
- 编译时内存使用持续增长
- IDE 变慢或崩溃
原因:
- 生成器中保存了对 Compilation 或 SemanticModel 的引用
- 缓存没有正确清理
解决方案:
csharp
// ❌ 错误:保存 Compilation 引用
[Generator]
public class LeakyGenerator : IIncrementalGenerator
{
private Compilation _compilation; // 内存泄漏!
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// ...
}
}
// ✅ 正确:不保存引用
[Generator]
public class CorrectGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// 只在管道中使用 Compilation
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) =>
{
// 在这里使用 ctx.SemanticModel
// 不要保存引用
return GetClassInfo(ctx);
});
}
}🔧 故障排除指南
系统化排查步骤
步骤 1: 验证生成器配置
xml
<!-- 检查项目引用配置 -->
<ItemGroup>
<ProjectReference Include="..\MyGenerator\MyGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- 检查生成器项目配置 -->
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsRoslynComponent>true</IsRoslynComponent>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>步骤 2: 检查编译输出
powershell
# 使用详细输出编译
dotnet build -v detailed > build.log
# 搜索生成器相关信息
Select-String -Path build.log -Pattern "Generator"步骤 3: 验证生成的文件
xml
<!-- 启用生成文件输出 -->
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>powershell
# 查看生成的文件
Get-ChildItem -Path obj\Debug\net8.0\generated -Recurse -File步骤 4: 使用诊断工具
csharp
// 添加诊断输出
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
// 报告处理信息
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor(
"SG9999",
"Generator Info",
"Processing: {0}",
"Generator",
DiagnosticSeverity.Info,
isEnabledByDefault: true),
Location.None,
classInfo.Name);
spc.ReportDiagnostic(diagnostic);
// 生成代码...
});常见问题快速参考
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 生成器不执行 | 项目引用配置错误 | 检查 OutputItemType="Analyzer" |
| 生成器不执行 | 特性未找到 | 使用完全限定名称 |
| 代码未生成 | predicate 过滤太严格 | 检查过滤条件 |
| 编译错误 | 生成的代码有语法错误 | 启用 EmitCompilerGeneratedFiles 检查 |
| 命名冲突 | 文件名重复 | 使用唯一的文件名 |
| 性能问题 | 使用传统生成器 | 迁移到增量生成器 |
| 性能问题 | 过度语义分析 | 在 predicate 中过滤 |
| 内存泄漏 | 保存 Compilation 引用 | 不要保存引用 |
| IDE 崩溃 | 生成器抛出异常 | 使用 try-catch 和诊断 |
| 调试困难 | 无法附加调试器 | 使用 Debugger.Launch() |
诊断命令速查
powershell
# 清理并重新编译
dotnet clean
dotnet build
# 详细编译输出
dotnet build -v detailed
# 查看生成的文件
Get-ChildItem obj\Debug\net8.0\generated -Recurse
# 查看编译器版本
dotnet --version
# 查看 SDK 信息
dotnet --info
# 恢复 NuGet 包
dotnet restore
# 清理 NuGet 缓存
dotnet nuget locals all --clear❓ 调试和诊断 FAQ
1. 如何知道生成器是否被调用?
答: 有多种方法:
方法 1: 使用日志
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
File.WriteAllText(
Path.Combine(Path.GetTempPath(), "generator-called.txt"),
$"Generator called at {DateTime.Now}");
// 生成器逻辑...
}方法 2: 使用诊断
csharp
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
spc.ReportDiagnostic(Diagnostic.Create(
new DiagnosticDescriptor(
"SG0001",
"Generator Called",
"Generator is processing class: {0}",
"Generator",
DiagnosticSeverity.Info,
isEnabledByDefault: true),
Location.None,
classInfo.Name));
});方法 3: 查看生成的文件
xml
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
</PropertyGroup>2. 为什么断点不会命中?
答: 可能的原因:
生成器未被调用
- 检查项目引用配置
- 验证特性是否正确
调试器未附加
- 使用
Debugger.Launch()强制附加 - 确保在 Debug 配置下编译
- 使用
代码已优化
- 在生成器项目中禁用优化:
xml<PropertyGroup> <Optimize>false</Optimize> </PropertyGroup>
3. 如何调试增量生成器的缓存问题?
答: 使用管道可视化:
csharp
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var classes = context.SyntaxProvider
.ForAttributeWithMetadataName(
"GenerateAttribute",
predicate: (node, _) => node is ClassDeclarationSyntax,
transform: (ctx, _) => GetClassInfo(ctx))
.WithTrackingName("ClassInfo"); // 添加跟踪名称
var filtered = classes
.Where(x => x != null)
.WithTrackingName("FilteredClasses"); // 添加跟踪名称
context.RegisterSourceOutput(filtered, GenerateCode);
}然后使用 dotnet build -v detailed 查看管道执行情况。
4. 如何测试生成器在不同场景下的行为?
答: 使用参数化测试:
csharp
[Theory]
[InlineData("public partial class MyClass { }", true)]
[InlineData("public class MyClass { }", false)] // 不是 partial
[InlineData("internal partial class MyClass { }", false)] // 不是 public
public void Generator_HandlesVariousClassDeclarations(string source, bool shouldGenerate)
{
var compilation = CreateCompilation(source);
var generator = new MyGenerator();
var driver = CSharpGeneratorDriver.Create(generator);
driver = driver.RunGeneratorsAndUpdateCompilation(
compilation,
out var outputCompilation,
out var diagnostics);
var runResult = driver.GetRunResult();
if (shouldGenerate)
{
Assert.NotEmpty(runResult.GeneratedTrees);
}
else
{
Assert.Empty(runResult.GeneratedTrees);
}
}5. 如何处理生成器中的异常?
答: 使用 try-catch 和诊断报告:
csharp
context.RegisterSourceOutput(classes, (spc, classInfo) =>
{
try
{
var code = GenerateCode(classInfo);
spc.AddSource($"{classInfo.Name}.g.cs", code);
}
catch (Exception ex)
{
// 报告错误诊断
var diagnostic = Diagnostic.Create(
new DiagnosticDescriptor(
"SG0001",
"Generator Error",
"Error generating code for {0}: {1}",
"Generator",
DiagnosticSeverity.Error,
isEnabledByDefault: true),
Location.None,
classInfo.Name,
ex.Message);
spc.ReportDiagnostic(diagnostic);
// 不要重新抛出异常
// throw; // ❌ 这会中断整个编译
}
});📝 调试清单
使用这个清单来系统地排查生成器问题:
基础检查
- [ ] 生成器项目目标框架是 netstandard2.0
- [ ] 类标记了 [Generator] 特性
- [ ] 实现了 ISourceGenerator 或 IIncrementalGenerator 接口
- [ ] 项目引用配置正确(OutputItemType="Analyzer")
- [ ] 清理并重新构建项目
代码检查
- [ ] Initialize 方法没有抛出异常
- [ ] Execute 方法没有抛出异常
- [ ] 生成的代码语法正确
- [ ] 文件名唯一且有效
- [ ] 使用了正确的编码(UTF-8)
性能检查
- [ ] 使用增量生成器(如果可能)
- [ ] 避免重复计算
- [ ] 使用缓存
- [ ] 避免不必要的语义分析
- [ ] 测量和优化热点代码
调试工具
- [ ] 启用详细构建输出
- [ ] 使用 Debugger.Launch()
- [ ] 添加日志输出
- [ ] 使用断点和跟踪点
- [ ] 查看生成的文件
🔗 相关资源
💡 关键要点
- 系统化排查 使用清单和步骤
- 检查配置 验证项目引用和生成器配置
- 查看输出 检查编译输出和生成的文件
- 使用诊断 报告详细的诊断信息
- 参考 FAQ 查看常见问题的解决方案
- 不要抛出异常 使用诊断报告错误
- 持续监控 性能和错误
最后更新: 2025-01-21