You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:10:42