由类重写的扩展方法不会发出警告

sim*_*sjo 19 c# extension-methods

我在另一个线程中进行了讨论,发现类方法优先于具有相同名称和参数的扩展方法.这很好,因为扩展方法不会劫持方法,但假设您已向第三方库添加了一些扩展方法:

public class ThirdParty
{
}

public static class ThirdPartyExtensions
{
    public static void MyMethod(this ThirdParty test)
    {
        Console.WriteLine("My extension method");
    }
}
Run Code Online (Sandbox Code Playgroud)

按预期工作:ThirdParty.MyMethod - >"我的扩展方法"

但是然后ThirdParty更新它的库并添加一个与扩展方法完全相同的方法:

public class ThirdParty
{
    public void MyMethod()
    {
        Console.WriteLine("Third party method");
    }
}

public static class ThirdPartyExtensions
{
    public static void MyMethod(this ThirdParty test)
    {
        Console.WriteLine("My extension method");
    }
}
Run Code Online (Sandbox Code Playgroud)

ThirdPart.MyMethod - >"第三方方法"

现在突然代码在运行时会表现不同,因为第三方方法"劫持"了你的扩展方法!编译器不会发出任何警告.

有没有办法启用此类警告或以其他方式避免这种情况?

Jon*_*eet 10

不 - 这是扩展方法的一个已知缺点,需要非常小心.我个人希望C#编译器在你声明一个永远不会通过普通静态路由(ExtensionClassName.MethodName(target, ...))来调用的扩展方法时会发出警告.

编写一个小工具来检查程序集中的所有扩展方法并以这种方式发出警告可能不会太难.它可能不需要非常精确地开始:只是警告如果已经有一个具有相同名称的方法(不用担心参数类型)将是一个良好的开端.

编辑:好的......这是一个非常粗糙的工具,至少可以给出一个起点.它似乎至少在一定程度上与泛型类型一起工作 - 但它并不试图对参数类型或名称做任何事情......部分原因是因为参数数组变得棘手.它还"完全"加载程序集而不是仅使用反射,这会更好 - 我尝试了"正确"的路线,但遇到了一些问题,这些问题并不是很容易解决,所以回到了快速而肮脏的路线: )

无论如何,希望它对某个人有用,某个地方.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

public class ExtensionCollisionDetector
{
    private static void Main(string[] args)
    {
        if (args.Length == 0)
        {
            Console.WriteLine
                ("Usage: ExtensionCollisionDetector <assembly file> [...]");
            return;
        }
        foreach (string file in args)
        {
            Console.WriteLine("Testing {0}...", file);
            DetectCollisions(file);
        }
    }

    private static void DetectCollisions(string file)
    {
        try
        {
            Assembly assembly = Assembly.LoadFrom(file);
            foreach (var method in FindExtensionMethods(assembly))
            {
                DetectCollisions(method);
            }
        }
        catch (Exception e)
        {
            // Yes, I know catching exception is generally bad. But hey,
            // "something's" gone wrong. It's not going to do any harm to
            // just go onto the next file.
            Console.WriteLine("Error detecting collisions: {0}", e.Message);
        }
    }

    private static IEnumerable<MethodBase> FindExtensionMethods
        (Assembly assembly)
    {
        return from type in assembly.GetTypes()
               from method in type.GetMethods(BindingFlags.Static |
                                              BindingFlags.Public |
                                              BindingFlags.NonPublic)
               where method.IsDefined(typeof(ExtensionAttribute), false)
               select method;
    }


    private static void DetectCollisions(MethodBase method)
    {
        Console.WriteLine("  Testing {0}.{1}", 
                          method.DeclaringType.Name, method.Name);
        Type extendedType = method.GetParameters()[0].ParameterType;
        foreach (var type in GetTypeAndAncestors(extendedType).Distinct())
        {
            foreach (var collision in DetectCollidingMethods(method, type))
            {
                Console.WriteLine("    Possible collision in {0}: {1}",
                                  collision.DeclaringType.Name, collision);
            }
        }
    }

    private static IEnumerable<Type> GetTypeAndAncestors(Type type)
    {
        yield return type;
        if (type.BaseType != null)
        {
            // I want yield foreach!
            foreach (var t in GetTypeAndAncestors(type.BaseType))
            {
                yield return t;
            }
        }
        foreach (var t in type.GetInterfaces()
                              .SelectMany(iface => GetTypeAndAncestors(iface)))
        {
            yield return t;
        }        
    }

    private static IEnumerable<MethodBase>
        DetectCollidingMethods(MethodBase extensionMethod, Type type)
    {
        // Very, very crude to start with
        return type.GetMethods(BindingFlags.Instance |
                               BindingFlags.Public |
                               BindingFlags.NonPublic)
                   .Where(candidate => candidate.Name == extensionMethod.Name);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @David:重点是*别人*正在破坏你的课程.例如,假设我在`Stream`上有一个`CopyTo`扩展方法.这在.NET 3.5中是完全合理的 - 但在.NET 4.0中没用.我的方法将不再被调用.现在,你是说我应该检查*.NET 4.0中引入的每一个新方法*,看看它是否会破坏我的扩展方法?*我的*代码没有改变......但它的行为有.`密封'在这里无关紧要.我用脚射击自己的方式是什么? (2认同)
  • @Eric:在这个假设的情况下,我们还在构建*我们不拥有的代码吗?如果是这种情况,请问我们在*any*警告中遇到同样的问题吗?例如,假设我们得到"添加新的或覆盖"警告..​​....我们是不是处于完全相同的情况? (2认同)