C# 类重写的扩展方法不会给出警告

C# 类重写的扩展方法不会给出警告,c#,extension-methods,C#,Extension Methods,我在另一个线程中进行了讨论,发现类方法优先于具有相同名称和参数的扩展方法。这很好,因为扩展方法不会劫持方法,但假设您已向第三方库添加了一些扩展方法: public class ThirdParty { } public static class ThirdPartyExtensions { public static void MyMethod(this ThirdParty test) { Console.WriteLine("My extension met

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

public class ThirdParty
{
}

public static class ThirdPartyExtensions
{
    public static void MyMethod(this ThirdParty test)
    {
        Console.WriteLine("My extension method");
    }
}
工作正常:ThirdParty.MyMethod->“我的扩展方法”

但第三方会更新其库并添加一个与扩展方法完全相同的方法:

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");
    }
}
ThirdPart.MyMethod->“第三方方法”

现在,由于第三方方法“劫持”了您的扩展方法,代码在运行时的行为会突然发生变化!编译器没有给出任何警告


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

没有-这是扩展方法的一个已知缺点,需要非常小心。就我个人而言,如果您声明了一个扩展方法,而该扩展方法只能通过正常的静态路由调用(
ExtensionClassName.MethodName(target…)
),我希望C#编译器会警告您

编写一个小工具来检查程序集中的所有扩展方法并以这种方式发出警告可能不会太难。一开始可能不需要非常精确:如果已经有同名的方法(不必担心参数类型),只需发出警告就可以了

编辑:好的。。。这是一个非常粗糙的工具,至少可以给出一个起点。它似乎至少在某种程度上适用于泛型类型-但它不尝试对参数类型或名称做任何事情。。。部分原因是使用参数数组会变得很棘手。它还“完全”加载程序集,而不是只加载反射,这会更好——我尝试了“正确”的方法,但遇到了一些问题,这些问题不是立即就能解决的,所以又回到了快速而肮脏的方法:)

不管怎样,希望它对某些人、某些地方有用

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);
    }
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
运用系统反思;
使用System.Runtime.CompilerServices;
公共类扩展冲突检测器
{
私有静态void Main(字符串[]args)
{
如果(args.Length==0)
{
控制台写入线
(“用法:ExtensionCollisionDetector[…]);
返回;
}
foreach(args中的字符串文件)
{
WriteLine(“测试{0}…”,文件);
检测碰撞(文件);
}
}
专用静态无效检测冲突(字符串文件)
{
尝试
{
Assembly=Assembly.LoadFrom(文件);
foreach(FindExtensionMethods(assembly)中的var方法)
{
检测碰撞(方法);
}
}
捕获(例外e)
{
//是的,我知道捕捉异常通常是不好的。但是嘿,
//“有些事”出了问题,这不会对你造成任何伤害
//请转到下一个文件。
WriteLine(“错误检测冲突:{0}”,e.Message);
}
}
私有静态IEnumerable FindExtensionMethods
(装配)
{
从程序集中的类型返回。GetTypes()
从type.GetMethods(BindingFlags.Static)中的方法|
BindingFlags.Public|
BindingFlags(非公开)
其中method.IsDefined(typeof(ExtensionAttribute),false)
选择方法;
}
私有静态无效检测冲突(MethodBase方法)
{
WriteLine(“测试{0}.{1}”,
method.DeclaringType.Name,method.Name);
类型extendedType=method.GetParameters()[0]。ParameterType;
foreach(GetTypeAndAncestors(extendedType).Distinct()中的变量类型)
{
foreach(DetectCollidingMethods中的var冲突(方法,类型))
{
WriteLine(“在{0}:{1}中可能发生冲突”,
collision.DeclaringType.Name,collision);
}
}
}
私有静态IEnumerable GetTypeAndAncestors(类型)
{
收益型;
if(type.BaseType!=null)
{
//我想要每一个!
foreach(GetTypeAndAncestors(type.BaseType))中的变量t)
{
收益率t;
}
}
foreach(type.GetInterfaces()中的var t)
.SelectMany(iface=>GetTypeAndAncestors(iface)))
{
收益率t;
}        
}
私有静态IEnumerable
DetectCollidingMethods(MethodBase extensionMethod,类型)
{
//一开始非常非常粗糙
返回类型.GetMethods(BindingFlags.Instance|
BindingFlags.Public|
BindingFlags(非公开)
.Where(candidate=>candidate.Name==extensionMethod.Name);
}
}

减少这种情况发生的一种可能的方法是在扩展方法名称的末尾包含缩写Ext。我知道,这很难看,但它减少了发生这种情况的机会

MyMethod_Ext


我喜欢乔恩的答案,但还有另一种类似丹尼尔的方法。如果有很多扩展方法,可以定义一个排序的“名称空间”。如果您有一个稳定的工作界面(即,如果您知道第三方不会改变,那么这种方法最有效)。然而,在您的情况下,您需要一个包装器类

我这样做是为了添加将字符串作为文件路径处理的方法。我定义了一个
文件系统
类型,它包装
字符串
,并提供属性和方法,如
IsAbsolute
ChangeExtension

在定义“扩展名称空间”时
MyMethodExt
// Enter my special namespace
public static MyThirdParty AsMyThirdParty(this ThirdParty source) { ... }

// Leave my special namespace
public static ThirdParty AsThirdParty(this MyThirdParty source) { ... }