自定义编译器警告


115

在.Net中使用ObsoleteAtribute时,它会向您提供编译器警告,告知您对象/方法/属性已过时,应使用其他方法。我目前正在从事一个需要大量重构前员工代码的项目。我想编写一个可用于标记方法或属性的自定义属性,该方法或属性将生成编译器警告,这些警告或警告将给出我编写的消息。像这样

[MyAttribute("This code sux and should be looked at")]
public void DoEverything()
{
}
<MyAttribute("This code sux and should be looked at")>
Public Sub DoEverything()
End Sub

我希望它生成一个编译器警告,指出“此代码为sux,应予以注意”。我知道如何创建自定义属性,问题是如何使它在Visual Studio中生成编译器警告。


这是C#吗?我要根据原始海报的意图推定,将其重新标记为C#(而不是C)。
Onorio Catenacci

13
那不是有效的VB或C#...那是什么...?!
ljs

5
旧问题,但是您现在可以使用Roslyn定义自定义编译器警告。
RJ Cuthbertson

4
@jrummell用Roslyn讲的代码分析器:johnkoerner.com/csharp/creating-your-first-code-analyzer
RJ Cuthbertson

2
@RJCuthbertson我已将您的评论移至可接受的答案中,以给予应有的关注。
jpaugh

Answers:


27

更新资料

Roslyn(Visual Studio 2015)现在可以做到这一点。你可以建立一个代码分析器来检查自定义属性


我不认为有可能。ObsoleteAttribute由编译器专门处理,并在C#标准中定义。为什么地球上的ObsoleteAttribute不可接受?在我看来,这恰恰是它的设计目标,并且可以满足您的要求!

还要注意,Visual Studio也会即时获取由ObsoleteAttribute生成的警告,这非常有用。

并不是要无益,只是想知道为什么您不热衷于使用它...

不幸的是ObsoleteAttribute是密封的(可能部分是由于特殊处理),因此您不能从中继承自己的属性。

从C#标准:

“过时”属性用于标记类型和不应再使用的类型成员。

如果程序使用用过时属性修饰的类型或成员,则编译器将发出警告或错误。具体来说,如果未提供错误参数,或者提供了错误参数且其值为false,则编译器会发出警告。如果指定了错误参数并且值为true,则编译器将发出错误。

那不是总结您的需求吗?...您做得不会比我想不到的要好。


14
我在找同样的东西。过时的“作品”,但是由于重构,代码并没有真正过时,甚至不完整。
g。

11
我同意@g,也许是原始作者。过时意味着过时,请勿使用。我想将其标记为“嘿,这可以编译,但我们确实需要a)完成功能或b)重构”。这将更多地是开发时间属性。任务也可以工作,例如// TODO :,但是我不使用那些,因为我猜很多人都没有,但是要定期查看编译器警告。
MikeJansen 2012年

8
不使用该[Obsolete]标记的另一个原因是,如果您需要对该属性进行XmlSerialization,则可能会导致问题。添加[Obsolete]标签还会[XmlIgnore]在幕后添加属性。
burnttoast12 2011年

6
过时的有所不同。过时的代码会在调用该方法的每一行代码上给您警告。我认为那不是发布者想要的(至少当我进行搜索并发现此问题时,这不是我想要的)。我以为问题是要在功能定义上显示警告,而不是在使用该功能的地方。
尼克

不是最大的答案。-1认为您无法提出不使用它的理由值得批评。这种态度不利于真实性。
Mike Socha III

96

这值得一试。

您不能扩展Obsolete,因为它是最终的,但也许您可以创建自己的属性,并将该类标记为过时,如下所示:

[Obsolete("Should be refactored")]
public class MustRefactor: System.Attribute{}

然后,当您使用“ MustRefactor”属性标记方法时,将显示编译警告。它生成一个编译时警告,但错误消息看起来很有趣,您应该自己查看并选择。这非常接近您想要实现的目标。

更新:使用此代码它会生成警告(不是很好,但是我认为没有更好的东西了)。

public class User
{
    private String userName;

    [TooManyArgs] // Will show warning: Try removing some arguments
    public User(String userName)
    {
        this.userName = userName;   
    }

    public String UserName
    {
        get { return userName; }
    }
    [MustRefactor] // will show warning: Refactor is needed Here
    public override string ToString()
    {
        return "User: " + userName;
    }
}
[Obsolete("Refactor is needed Here")]
public class MustRefactor : System.Attribute
{

}
[Obsolete("Try removing some arguments")]
public class TooManyArgs : System.Attribute
{

}

您可以粘贴生成的内容吗?我很好奇。
米卡

1
即使未调用属性/方法,也会触发编译警告。
罗尔夫·克里斯滕森

1
好建议在这里。我一直想做同样的事情,最后只抛出NotImplementedExceptions。这不是最佳解决方案,因为它们不会在编译时显示,仅在运行时(如果碰巧执行了代码)才会显示。我自己尝试一下。
MonkeyWrench 2011年

1
如果ObsolteAttribute可以像DebuggerDisplayAttribute一样支持表达式不是很好,那么我们真的可以做一些很酷的事情。visualstudio.uservoice.com/forums/121579-visual-studio/…–
jpierson

如果您IDisposable在那些过时的类上实现,则意味着您可以将不可靠的测试代码包装在一个using块中。像这样:using(new MustRefactor()){DodgyCode();}。完成后,您可以找到所有用法。我现在Sleep在for循环中的线程上使用此线程,我需要人为地减慢速度以进行调试。
伊恩·弗雷泽

48

在某些编译器中,您可以使用#warning发出警告:

#warning "Do not use ABC, which is deprecated. Use XYZ instead."

在Microsoft编译器中,通常可以使用消息编译指示:

#pragma message ( "text" )

您提到过.Net,但未指定是使用C / C ++还是C#进行编程。如果您使用C#编程,那么您应该知道C#支持#warning格式


1
#warning或#pragma是预处理程序指令,因此无论micah的前同事代码是否存在,它们都将运行,并且它根本不与属性交互。可以肯定的是过时是实现这一目标的唯一方法...
ljs

39

我们目前处于大量重构的中间,无法立即修复所有问题。我们只使用#warning preproc命令,需要返回到代码中。它显示在编译器输出中。我认为您不能将其放在方法上,但是可以将其放在方法内部,并且仍然很容易找到。

public void DoEverything() {
   #warning "This code sucks"
}

7

在VS 2008(+ sp1)中,#警告未在Clean Soultion&Rebuild Solution解决方案后的错误列表中正确显示,也没有全部。只有打开特定的类文件后,某些警告才会显示在错误列表中。所以我被迫使用自定义属性:

[Obsolete("Mapping ToDo")]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property)]
public class MappingToDo : System.Attribute
{
    public string Comment = "";

    public MappingToDo(string comment)
    {
        Comment = comment;
    }

    public MappingToDo()
    {}
}

所以当我用它标记一些代码时

[MappingToDo("Some comment")]
public class MembershipHour : Entity
{
    // .....
}

它会产生如下警告:

Namespace.MappingToDo已过时:'Mapping ToDo'。

我无法更改警告文字,“某些评论”未显示错误列表。但是它将跳到文件中的适当位置。因此,如果您需要更改此类警告消息,请创建各种属性。


6

您试图做的是滥用属性。而是使用Visual Studio任务列表。您可以在代码中输入如下注释:

//TODO:  This code sux and should be looked at
public class SuckyClass(){
  //TODO:  Do something really sucky here!
}

然后从菜单中打开“查看/任务列表”。任务列表有两个类别,用户任务和注释。切换到评论,您将看到所有// Todo:在​​那里。双击TODO将跳转到代码中的注释。


1
我发现这是一个更可取的解决方案
Samuel

1
如果要将功能标记为“在生产代码中不被调用”或类似的功能,该怎么办。因此,如果要调用或实例化一个函数或类,则希望它触发,但如果只是编译则不希望它触发。
杰西·派珀

2

我认为你不能。据我所知,对ObsoleteAttribute的支持本质上是硬编码到C#编译器中的。您不能直接做任何类似的事情。

您可能能够做的是使用MSBuild任务(或构建后事件),该任务对刚刚编译的程序集执行自定义工具。自定义工具将反映出程序集中的所有类型/方法并使用您的自定义属性,此时它可以打印到System.Console的默认或错误TextWriters。


2

查看ObsoleteAttribute的源代码,看起来它并没有做任何特别的事情来生成编译器警告,因此我倾向于使用@ technophile并说它是硬编码到编译器中的。您是否有理由不想仅使用ObsoleteAttribute生成警告消息?


除代码外,没有其他特定原因未必会过时。
米卡

1
它在C#规范中指定为由编译器专门处理,请查看我的回答:-)。Micah-“过时的属性用于标记类型和不应再使用的类型的成员。” 从规格。这不适用吗?...
ljs

就像有人想知道的那样,源代码中也没有C#代码可以执行此操作。referencesource.microsoft.com/#mscorlib/system/...
帕维尔马赫

1

有几条建议建议插入警告或杂注。过时的方式大相径庭!如果标记为过时的库L的功能,则即使调用者程序不在库L中,程序调用该函数时也会出现过时的消息。仅当编译L时,警告才产生该消息。


1

这是Roslyn实现,因此您可以创建自己的属性,这些属性可以即时发出警告或错误。

我创建了一个属性Type Called IdeMessage,它将是生成警告的属性:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class IDEMessageAttribute : Attribute
{
    public string Message;

    public IDEMessageAttribute(string message);
}

为此,您需要先安装Roslyn SDK,然后使用分析器启动一个新的VSIX项目。我已经省略了一些不太相关的信息,例如消息,您可以弄清楚该怎么做。在分析仪中,您可以执行此操作

public override void Initialize(AnalysisContext context)
{
    context.RegisterSyntaxNodeAction(AnalyzerInvocation, SyntaxKind.InvocationExpression);
}

private static void AnalyzerInvocation(SyntaxNodeAnalysisContext context)
{
    var invocation = (InvocationExpressionSyntax)context.Node;

    var methodDeclaration = (context.SemanticModel.GetSymbolInfo(invocation, context.CancellationToken).Symbol as IMethodSymbol);

    //There are several reason why this may be null e.g invoking a delegate
    if (null == methodDeclaration)
    {
        return;
    }

    var methodAttributes = methodDeclaration.GetAttributes();
    var attributeData = methodAttributes.FirstOrDefault(attr => IsIDEMessageAttribute(context.SemanticModel, attr, typeof(IDEMessageAttribute)));
    if(null == attributeData)
    {
        return;
    }

    var message = GetMessage(attributeData); 
    var diagnostic = Diagnostic.Create(Rule, invocation.GetLocation(), methodDeclaration.Name, message);
    context.ReportDiagnostic(diagnostic);
}

static bool IsIDEMessageAttribute(SemanticModel semanticModel, AttributeData attribute, Type desiredAttributeType)
{
    var desiredTypeNamedSymbol = semanticModel.Compilation.GetTypeByMetadataName(desiredAttributeType.FullName);

    var result = attribute.AttributeClass.Equals(desiredTypeNamedSymbol);
    return result;
}

static string GetMessage(AttributeData attribute)
{
    if (attribute.ConstructorArguments.Length < 1)
    {
        return "This method is obsolete";
    }

    return (attribute.ConstructorArguments[0].Value as string);
}

没有为此的CodeFixProvider,您可以将其从解决方案中删除。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.