C#:如何创建替代密封ObsoleteAttribute的自定义编译器警告属性
嘿,这个问题我刚好踩过坑!因为ObsoleteAttribute确实是密封类没法直接继承,但咱们有个完美的替代方案——用自定义属性+Roslyn代码分析器的组合,完全能实现你想要的所有效果:自定义属性名、自定义警告提示,甚至还能在VS里保留那种熟悉的绿色下划线!
解决方案:自定义属性 + Roslyn代码分析器
1. 创建你的自定义属性类
首先定义一个属于自己的属性,用来标记需要“废弃”的成员,还可以自定义提示消息:
using System; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event, Inherited = false, AllowMultiple = false)] public class MyDeprecatedAttribute : Attribute { // 自定义警告提示语 public string Message { get; } public MyDeprecatedAttribute(string message) { Message = message; } // 可选:如果需要类似Obsolete的"强制错误"开关,可以加个IsError属性 public bool IsError { get; set; } }
2. 编写Roslyn分析器触发警告&下划线
这是核心步骤——通过Roslyn分析器,在编译时扫描所有带有MyDeprecatedAttribute的成员,主动发出警告(或错误),同时VS会自动给这些成员加上绿色下划线(和Obsolete的效果完全一致)。
核心分析器代码示例:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Linq; [DiagnosticAnalyzer(LanguageNames.CSharp)] public class MyDeprecatedAnalyzer : DiagnosticAnalyzer { // 自定义诊断ID(保证唯一即可) public const string DiagnosticId = "MYDEPRECATED001"; // 配置诊断的基础信息 private static readonly LocalizableString Title = "成员已被标记为废弃"; private static readonly LocalizableString MessageFormat = "{0}"; private static readonly LocalizableString Description = "使用了被标记为废弃的成员"; private const string Category = "Usage"; private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor( DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, // 设为Warning是警告,设为Error就是编译错误 isEnabledByDefault: true, description: Description); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { // 启用基础配置 context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.EnableConcurrentExecution(); // 注册分析逻辑:扫描所有被引用的标识符(比如调用方法、访问属性) context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.IdentifierName); } private static void AnalyzeNode(SyntaxNodeAnalysisContext context) { var identifierName = (IdentifierNameSyntax)context.Node; // 获取标识符对应的成员符号 var symbol = context.SemanticModel.GetSymbolInfo(identifierName).Symbol; if (symbol == null) return; // 检查该成员是否带有我们的自定义属性 var deprecatedAttr = symbol.GetAttributes().FirstOrDefault(attr => attr.AttributeClass?.Name == nameof(MyDeprecatedAttribute)); if (deprecatedAttr == null) return; // 提取自定义提示消息 var message = deprecatedAttr.ConstructorArguments.FirstOrDefault().Value?.ToString() ?? "该成员已被标记为废弃"; // 动态调整诊断级别(如果设置了IsError) var severity = DiagnosticSeverity.Warning; var isErrorArg = deprecatedAttr.NamedArguments.FirstOrDefault(arg => arg.Key == nameof(MyDeprecatedAttribute.IsError)); if (isErrorArg.Value.Value is bool isError && isError) { severity = DiagnosticSeverity.Error; } // 触发诊断:这一步会生成编译器警告/错误,同时VS显示绿色下划线 var diagnostic = Diagnostic.Create(Rule, identifierName.GetLocation(), message); context.ReportDiagnostic(diagnostic); } }
3. 测试效果
现在你就可以在项目里用自定义属性标记成员了:
public class MyClass { [MyDeprecated("这个方法已经过时,请使用NewMethod代替")] public void OldMethod() { } public void NewMethod() { } }
当你在代码里调用OldMethod()时,VS会给它加上绿色下划线,编译时也会弹出你自定义的警告提示语,完全和ObsoleteAttribute的行为一致!
额外提示
- 如果想把这个分析器分享给其他项目用,可以把分析器打包成NuGet包,引用后就能直接使用。
- 你还可以扩展自定义属性的功能,比如添加
Replacement属性,提示用户应该用哪个新成员替代,然后在分析器的警告消息里自动显示。
内容的提问来源于stack exchange,提问作者aTrusty
相关产品推荐
相关产品推荐

