关于c#:使第三方库中的扩展方法过时

Making extension methods from a third party library obsolete

这个问题不是关于我可以用[System.Obsolete]标记的方法。我要忽略的方法在一个我无法控制的dll中。

我使用包含对象扩展方法的第三方库。这会导致混乱,并可能在将来引起问题。是否有任何方法可以将此扩展方法(或来自某个dll的所有扩展方法)标记为外部过时,或阻止此扩展方法在IntelliSense中出现。有问题的方法是:

1
2
3
4
5
6
7
8
9
    public static class ExtensionMethods
    {
      public static bool IsNumeric(this object obj)
      {
        if (obj == null)
          return false;
        return obj.GetType().IsPrimitive || obj is double || (obj is Decimal || obj is DateTime) || obj is TimeSpan;
      }
    }


您可以使用Roslyn代码分析器来实现这一点。下面的代码将创建一个DiagnosticAnalyzer,如果使用String.EndsWith(),它将给出编译器警告。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ForbiddenMethodsAnalyzer : DiagnosticAnalyzer
{
    private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor("Forbidden",
                                                                                "Don't use this method!",
                                                                                "Use of the '{0}' method is not allowed",
                                                                                "Forbidden.Stuff",
                                                                                 DiagnosticSeverity.Warning,
                                                                                 isEnabledByDefault: true,
                                                                                 description:"This method is forbidden");
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } }

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

    private static void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
    {
        var invocationExpression = (InvocationExpressionSyntax)context.Node;
        var memberAccessExpression = invocationExpression.Expression as MemberAccessExpressionSyntax;
        if (memberAccessExpression?.Name.ToString() =="EndsWith")
        {
            var memberSymbol = context.SemanticModel.GetSymbolInfo(memberAccessExpression).Symbol as IMethodSymbol;
            var containingType = memberSymbol.ContainingType;
            if (containingType.ContainingNamespace.Name =="System" && containingType.Name =="String")
            {
                var diagnostic = Diagnostic.Create(Rule, invocationExpression.GetLocation(), memberAccessExpression.ToString());
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}

Tooltip warningError List warning

使用这样的分析器有3个选项:

  • 直接将DiagnosticAnalyzer代码添加到项目中。它只适用于该解决方案。
  • 创建一个包含DiagnosticAnalyzer的类库,并分发作为Nuget包。它只适用于使用该包的解决方案。
  • 编译包含班级。分析器将处理您加载的任何解决方案。

这是我所做的第一个使用Roslyn代码分析功能的项目,因此很遗憾我不了解这里发生的一切。我从默认的分析器模板开始,尝试了各种方法,逐步执行代码,并使用监视窗口查看变量,直到找到实现此功能所需的信息。

基本过程是注册一个syntaxnode分析函数,过滤到调用方法的表达式。在这种方法中,我检查被检查的MemberAccessExpressionSyntaxName是否"结束"。如果是的话,我会得到方法为on的ContainingType,并检查它是否在System名称空间中的String类上。如果是的话,我从一个DiagnosticDescriptor创建一个Diagnostic实例来告诉IDE问题在哪里,以及它代表了多少问题(在本例中是一个警告,如果我愿意,我可以使它成为一个完全错误,这将阻止代码编译)。也可以为用户提供不同的选项来自动修复错误,但我还没有对此进行探讨。

本教程提供了大量信息,以及大量的尝试和错误。


处理这种情况的最佳方法是使用Roslyn并创建自己的代码分析器,或者使用现有的工具(如fxcop)。

然而,我发现了一个非常不优雅的解决方法。

在项目中,可以使用完全相同的方法创建与被引用类同名的类,该类位于相同的命名空间中。现在将您的方法标记为已过时。

下面的代码示例引用了在External命名空间中定义的具有ExtensionMethods类的库。在标有(*)注释的行中,如果使用静态方法调用语法调用方法,编译器会警告您类型ExtensionMethods与导入的类型冲突。它还告诉您该方法已过时(因为您已经隐藏了导入的类型,所以它可以看到您的定义)。因此,当调用该方法时,代码将运行。在用(**)注释标记的行中,使用扩展方法调用语法调用方法,编译器说调用不明确,代码无法编译。我唯一知道的解决方法是将此调用转换为行(*),这将产生过时的警告。

使用此解决方案,如果使用扩展方法语法,则可以从引用的类型调用其他扩展方法,前提是类中没有定义相同的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using System;
using External;

namespace Internal
{
    class Program
    {
        static void Main(string[] args)
        {
            ExtensionMethods.IsNumeric(new object()); // (*)
            new object().IsNumeric(); // (**)
        }
    }
}

namespace External
{
    public static class ExtensionMethods
    {
        [Obsolete]
        public static bool IsNumeric(this object o)
        {
            if (obj == null)
              return false;
            return obj.GetType().IsPrimitive || obj is double || (obj is Decimal || obj is DateTime) || obj is TimeSpan;
        }
    }
}