傳統靜態分析 (FxCop/Code Analysis) 僅在建置後執行,回饋太慢。Roslyn (.NET Compiler Platform) 開放編譯器管線 API,讓開發者能在**輸入程式碼當下**即時偵測違規、顯示紅波浪線、甚至提供一鍵自動修復。本文以「禁止使用 DateTime.Now、強制 UTC」為例,完整示範 Code Issue Provider 與 Code Action 開發流程。

DiagnosticAnalyzer (偵測) 與 CodeFixProvider (修復) 兩大核心類別。
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class DateTimeNowAnalyzer : DiagnosticAnalyzer {
public static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(
"DATETIME001", "避免使用 DateTime.Now", "請改用 DateTime.UtcNow",
"Usage", DiagnosticSeverity.Error, true);
public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule);
public override void Initialize(AnalysisContext context) {
context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression);
}
private void AnalyzeMemberAccess(SyntaxNodeAnalysisContext ctx) {
var memberAccess = (MemberAccessExpressionSyntax)ctx.Node;
// 1. 必須是「.Now」存取
if (memberAccess.Name.Identifier.ValueText != "Now") return;
if (memberAccess.OperatorToken.Kind() != SyntaxKind.DotToken) return;
// 2. 語義模型確認型別為 System.DateTime (mscorlib)
var symbolInfo = ctx.SemanticModel.GetSymbolInfo(memberAccess.Name);
var propSymbol = symbolInfo.Symbol as IPropertySymbol;
if (propSymbol == null) return;
if (propSymbol.ContainingType.ToDisplayString() != "System.DateTime") return;
if (!propSymbol.ContainingAssembly.Name.Equals("mscorlib", StringComparison.OrdinalIgnoreCase)) return;
// 3. 回報診斷
ctx.ReportDiagnostic(Diagnostic.Create(Rule, memberAccess.Name.GetLocation()));
}
}
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DateTimeNowCodeFixProvider)), Shared]
public class DateTimeNowCodeFixProvider : CodeFixProvider {
public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create("DATETIME001");
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) {
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
// 找到 Now 識別碼節點
var identifier = root.FindToken(diagnosticSpan.Start).Parent as IdentifierNameSyntax;
if (identifier == null) return;
context.RegisterCodeFix(
CodeAction.Create("改用 UtcNow", c => ReplaceNowWithUtcNow(context.Document, identifier, c), "DATETIME001_FIX"),
diagnostic);
}
private Task ReplaceNowWithUtcNow(Document doc, IdentifierNameSyntax nowNode, CancellationToken ct) {
var utcNowNode = SyntaxFactory.IdentifierName("UtcNow")
.WithTriviaFrom(nowNode); // 保留原有空白/註解
var newRoot = root.ReplaceNode(nowNode, utcNowNode);
return Task.FromResult(doc.WithSyntaxRoot(newRoot));
}
}
同樣模式偵測 ObjectCreationExpressionSyntax:
DateTimeKind → 產生 Unspecified 型別,報錯建議加上 DateTimeKind.Utc。DateTimeKind.Local 或 Unspecified → 報錯並提供自動修正為 Utc。
private void AnalyzeObjectCreation(SyntaxNodeAnalysisContext ctx) {
var creation = (ObjectCreationExpressionSyntax)ctx.Node;
if (creation.Type.ToString() != "DateTime") return;
var symbolInfo = ctx.SemanticModel.GetSymbolInfo(creation);
var ctor = symbolInfo.Symbol as IMethodSymbol;
if (ctor == null || ctor.ContainingType.ToDisplayString() != "System.DateTime") return;
// 找 DateTimeKind 參數
var kindArg = creation.ArgumentList.Arguments
.FirstOrDefault(a => a.Expression is MemberAccessExpressionSyntax maes &&
ctx.SemanticModel.GetSymbolInfo(maes).Symbol?.ContainingType.ToDisplayString() == "System.DateTimeKind");
if (kindArg != null) {
var kindValue = (kindArg.Expression as MemberAccessExpressionSyntax)?.Name.Identifier.ValueText;
if (kindValue == "Local" || kindValue == "Unspecified") {
ctx.ReportDiagnostic(Diagnostic.Create(Rule, kindArg.GetLocation(), "請使用 DateTimeKind.Utc"));
}
} else {
// 無 DateTimeKind 參數
ctx.ReportDiagnostic(Diagnostic.Create(Rule, creation.GetLocation(), "建構函式必須指定 DateTimeKind.Utc"));
}
}
var n = DateTime.Now; → 紅波浪線 + 燈泡圖示 → 點選「改用 UtcNow」 → 自動變更為 DateTime.UtcNow。Microsoft.CodeAnalysis.Testing 驗證診斷與修復行為。Initialize 中以 RegisterSyntaxNodeAction 限定節點類型 (如 SimpleMemberAccessExpression),避免掃描所有節點。CancellationToken,長時間分析時響應取消。Roslyn 將編譯器知識民主化,讓團隊能將編碼規範 (命名慣例、安全模式、效能禁忌、領域特定規則) 轉化為**即時、可自動修復**的開發體驗。雖文件尚待完善,但語法視覺化工具與社群範例已足夠上手。投資建立專屬 Analyzer,長期將大幅降低 Code Review 成本、統一代碼風格、預防生產環境 Bug。
下載完整範例專案,立即啟動你的 Roslyn 擴充功能開發之旅。