最佳实践
本文档总结使用诊断 API 时的最佳实践。
文档信息
- 难度级别: 中级
- 预计阅读时间: 15 分钟
🎯 学习目标
- ✅ 掌握 DiagnosticDescriptor 最佳实践
- ✅ 学会 Diagnostic 报告最佳实践
- ✅ 了解 DiagnosticAnalyzer 最佳实践
DiagnosticDescriptor 最佳实践
- 使用一致的 ID 命名约定
- 提供清晰的标题和消息
- 选择合适的严重级别
- 提供详细的描述和帮助链接
- 使用标准类别
- 合理设置默认启用
- 使用参数化消息
- 集中管理描述符
- 使用常量定义
- 添加遥测标签
Diagnostic 报告最佳实践
- 使用精确的位置
- 提供有用的消息参数
- 使用附加位置显示相关代码
- 验证位置有效性
- 避免重复报告
- 使用合适的分析上下文
- 考虑性能影响
- 提供诊断属性
- 处理生成的代码
- 使用描述性的 ID
DiagnosticAnalyzer 最佳实践
- 启用并发执行
- 配置生成代码分析
- 使用合适的分析动作
- 避免耗时操作
- 使用取消令牌
- 缓存语义信息
- 使用不可变集合
- 处理边界情况
- 编写单元测试
- 提供代码修复
示例:完整的最佳实践
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BestPracticeAnalyzer : DiagnosticAnalyzer
{
// 1. 使用常量定义
private const string DiagnosticId = "BP001";
private const string Category = "Naming";
// 2. 集中管理描述符
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: DiagnosticId,
title: "类名应该使用 PascalCase",
messageFormat: "类名 '{0}' 应该使用 PascalCase 命名",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "类名应该遵循 PascalCase 命名约定。",
helpLinkUri: "https://docs.example.com/naming");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
// 3. 配置生成代码分析
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
// 4. 启用并发执行
context.EnableConcurrentExecution();
// 5. 使用合适的分析动作
context.RegisterSyntaxNodeAction(
AnalyzeClass,
SyntaxKind.ClassDeclaration);
}
private void AnalyzeClass(SyntaxNodeAnalysisContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
var className = classDeclaration.Identifier.Text;
// 6. 快速检查
if (IsPascalCase(className))
return;
// 7. 使用精确的位置
var location = classDeclaration.Identifier.GetLocation();
// 8. 验证位置
if (location == null || location.IsInMetadata)
return;
// 9. 创建诊断
var diagnostic = Diagnostic.Create(
Rule,
location,
className);
// 10. 报告诊断
context.ReportDiagnostic(diagnostic);
}
private bool IsPascalCase(string name)
{
return !string.IsNullOrEmpty(name) && char.IsUpper(name[0]);
}
}反模式总结
DiagnosticDescriptor 反模式
- 使用不一致的 ID
- 消息过于简单或模糊
- 滥用 Error 级别
- 没有提供帮助信息
- 使用不明确的类别
Diagnostic 报告反模式
- 使用过大的位置范围
- 消息参数不匹配
- 在元数据位置报告
- 重复报告同一问题
- 不提供有用的消息
DiagnosticAnalyzer 反模式
- 不启用并发执行
- 不配置生成代码分析
- 使用错误的分析动作
- 执行耗时操作
- 不使用取消令牌
相关文档
- 上一篇: 消息格式化
- 返回: 诊断 API 索引
- 相关: DiagnosticDescriptor 详解
- 相关: Diagnostic 报告
- 相关: DiagnosticAnalyzer 实现
消息格式化最佳实践
- 使用清晰的语言 - 避免技术术语
- 提供具体信息 - 包含相关的名称、类型等
- 使用参数占位符 - 提高灵活性
- 处理复数形式 - 根据语言选择合适的方式
- 考虑本地化 - 如果需要支持多语言,使用资源文件
- 保持消息简洁 - 不要过长
- 提供建议 - 告诉用户如何修复
- 使用一致的格式 - 所有消息使用相同的风格
- 避免重复信息 - 不要在消息中重复标题
- 测试消息显示 - 确保消息在 IDE 中正确显示
性能优化最佳实践
- 启用并发执行 -
context.EnableConcurrentExecution() - 使用合适的分析动作 - 选择最合适的分析阶段
- 快速检查后再详细分析 - 避免不必要的耗时操作
- 缓存语义查询结果 - 避免重复查询
- 使用取消令牌 - 允许 IDE 取消长时间运行的分析
- 避免在循环中进行语义查询 - 性能影响大
- 使用不可变集合 - 线程安全
- 处理边界情况 - 检查 null、空集合等
- 编写单元测试 - 确保分析器正确工作
- 监控性能指标 - 确保分析器不影响 IDE 性能
完整的最佳实践示例
csharp
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class CompleteBestPracticeAnalyzer : DiagnosticAnalyzer
{
// 1. 使用常量定义
private const string DiagnosticId = "BP001";
private const string Category = "Naming";
// 2. 集中管理描述符
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
id: DiagnosticId,
title: "类名应该使用 PascalCase",
messageFormat: "类名 '{0}' 应该使用 PascalCase 命名",
category: Category,
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "类名应该遵循 PascalCase 命名约定。",
helpLinkUri: "https://docs.example.com/naming");
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context)
{
// 3. 配置生成代码分析
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
// 4. 启用并发执行
context.EnableConcurrentExecution();
// 5. 使用合适的分析动作
context.RegisterSyntaxNodeAction(
AnalyzeClass,
SyntaxKind.ClassDeclaration);
}
private void AnalyzeClass(SyntaxNodeAnalysisContext context)
{
var classDeclaration = (ClassDeclarationSyntax)context.Node;
var className = classDeclaration.Identifier.Text;
// 6. 快速检查
if (IsPascalCase(className))
return;
// 7. 使用精确的位置
var location = classDeclaration.Identifier.GetLocation();
// 8. 验证位置
if (location == null || location.IsInMetadata)
return;
// 9. 创建诊断
var diagnostic = Diagnostic.Create(
Rule,
location,
className);
// 10. 报告诊断
context.ReportDiagnostic(diagnostic);
}
private bool IsPascalCase(string name)
{
return !string.IsNullOrEmpty(name) && char.IsUpper(name[0]);
}
}测试最佳实践
- 使用 Microsoft.CodeAnalysis.Testing 包
- 测试正常情况和边界情况
- 验证诊断位置的准确性
- 测试消息参数的正确性
- 测试性能影响
csharp
using Microsoft.CodeAnalysis.Testing;
using Xunit;
public class AnalyzerTests
{
[Fact]
public async Task TestClassNameAnalyzer()
{
var test = @"
class myClass { } // 应该报告诊断
";
var expected = DiagnosticResult
.CompilerWarning("BP001")
.WithSpan(2, 19, 2, 26)
.WithArguments("myClass");
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}
[Fact]
public async Task TestPascalCaseClass_NoDiagnostic()
{
var test = @"
class MyClass { } // 不应该报告诊断
";
await VerifyCS.VerifyAnalyzerAsync(test);
}
}文档最佳实践
- 提供清晰的文档 - 说明诊断的目的和修复方法
- 添加帮助链接 - 提供更多信息的链接
- 提供代码示例 - 展示正确和错误的用法
- 说明配置选项 - 如何启用/禁用诊断
- 维护更新日志 - 记录诊断规则的变更
常见问题
Q: 如何确保分析器性能?
A:
- 启用并发执行
- 使用合适的分析动作
- 快速检查后再详细分析
- 避免耗时操作
- 使用取消令牌
Q: 如何测试分析器?
A: 使用 Microsoft.CodeAnalysis.Testing 包:
csharp
[Fact]
public async Task TestAnalyzer()
{
var test = "...";
var expected = DiagnosticResult.CompilerWarning("ID001");
await VerifyCS.VerifyAnalyzerAsync(test, expected);
}Q: 如何处理生成的代码?
A: 使用 ConfigureGeneratedCodeAnalysis:
csharp
context.ConfigureGeneratedCodeAnalysis(
GeneratedCodeAnalysisFlags.None);Q: 如何提供代码修复?
A: 实现 CodeFixProvider:
csharp
[ExportCodeFixProvider(LanguageNames.CSharp)]
public class MyCodeFixProvider : CodeFixProvider
{
// 实现代码修复逻辑
}总结
遵循这些最佳实践可以帮助你创建高质量的诊断分析器:
- 清晰的诊断定义 - 使用一致的 ID 和清晰的消息
- 精确的位置指定 - 只标记有问题的部分
- 良好的性能 - 启用并发执行,避免耗时操作
- 完善的测试 - 确保分析器正确工作
- 详细的文档 - 帮助用户理解和使用诊断