使用反射(C#)检测方法是否被覆盖

68

假设我有一个基类TestBase,在这个类中我定义了一个虚方法TestMe()

class TestBase
{
    public virtual bool TestMe() {  }
}

现在我继承了这个类:

class Test1 : TestBase
{
    public override bool TestMe() {}
}

现在,使用反射机制,我需要找出TestMe方法是否被子类覆盖-这可能吗?

我所需的是编写一个类型为“对象”的设计师可视化工具,以展示整个继承层次结构,并显示哪些虚拟方法在哪个层次级别被覆盖。


我不知道具体如何实现,但一定有类似的方法。有一个名为"RedGate Reflector" 的优秀工具可以展示库中某个方法的逻辑。 - Vivian River
9个回答

77

对于类型Test1,您可以确定它是否有自己的实现声明TestMe

typeof(Test1).GetMethod("TestMe").DeclaringType == typeof(Test1)
如果声明来自基本类型,这将评估为false。
请注意,由于这是测试声明而不是真正的实现,如果Test1也是抽象的并且TestMe是抽象的,那么这将返回true,因为Test1将有其自己的声明。如果您想排除该情况,请添加&&!GetMethod(“TestMe”)。IsAbstract

25
这个解决方案不完整。它没有涵盖Test1声明具有相同名称但参数不同的方法的情况。如果上面的测试结果为真,你只知道Test1有一个名为TestMe的方法,但你不知道它是否是一个覆盖方法。你还需要使用GetBaseDefinition()方法。如果这个调用返回一个DeclaringType == typeof(TestBase)的MethodInfo对象,那么你才能确定你有一个覆盖方法。 - Ciprian Bortos
2
@Ciprian 这不是一个完整的代码解决方案,只是解释了在哪里找到反射的相关部分来实现它。 - Rex M
4
是的,我看了一下,它不正确/非常有限,而且从你的代码到一个可行的解决方案还需要相当多的工作。你甚至没有提及像GetBaseDefinition()这样必要的API,以及与方法隐藏或不同方法签名相关的问题。 - CodesInChaos
1
如果您想在抽象基类中检查派生类是否覆盖了某个方法,可以使用 this.GetType 进行检查,例如:this.GetType().GetMethod("MethodName").DeclaringType == this.GetType() - Justin

39

我尝试了Ken Beckett提出的解决方案,但并未成功。以下是我最终采用的方法:

    public static bool IsOverride(MethodInfo m) {
        return m.GetBaseDefinition().DeclaringType != m.DeclaringType;
    }

这里有gist中的测试。


2
非常好用。非常感谢! 关于获取MethodInfo实例的一点说明。我最初犯了一个错误,通过以下方式获取它: typeof(SomeType).GetMethod(someFunctionName) 使用此MethodInfo实例时IsOverride不起作用。你需要这样做: someTypeInstance.GetType().GetMethod(someFunctionName) 当然,这是完全合理的,但仍然有些微妙。显然,在调用GetType()时,返回的Type对象中保留了对实例的引用。 - Dimitri C.
@DimitriC。我正在使用Assembly.Load([myAssembleyNameHere]).GetTypes(),它运行得非常完美.. 应该被标记为已接受的解决方案。 - AZ_
@DimitriC。不,这里展示的技术中,没有任何方式将引用实例“保存在返回的Type对象中”;事实上恰恰相反:每个运行时实例都知道其实际的Type,您可以通过GetType()获得(准确地说,在运行时类型实例的TypeRuntimeType,这是从Type派生的类型)。好消息是,尽管您的解释有些小问题,但您提供的代码在技术上是获得真正100%正确行为的唯一方法,因此它是本页面上最重要的宝石之一... - Glenn Slayden
因为此页面上的大多数示例仅考虑静态类型,例如 typeof(SomeType),因此它们将无法处理任何/所有实际重写该方法的 SomeType 派生类的实例,即使 SomeType 可能没有这样做。由于任何 SomeType 派生实例都有可能出现,因此您需要在每次调用时通过 GetType() 独立检查每个运行时实例。不这样做可能是导致您最初提到的问题的原因。 - Glenn Slayden
运行得非常好。这应该是被接受的答案。 - Arshia001

28

正如 @CiprianBortos 指出的那样,被接受的答案不完整,如果直接使用它,将会导致代码中的严重错误。

他的评论提供了神奇的解决方案 GetBaseDefinition(),但是如果你想要一个通用的 IsOverride 检查(我认为这就是这个问题的点),就没有必要检查 DeclaringType 了,只需要使用 methodInfo.GetBaseDefinition() != methodInfo 即可。

或者,作为 MethodInfo 的扩展方法提供,我认为以下代码可以解决问题:

public static class MethodInfoUtil
{
    public static bool IsOverride(this MethodInfo methodInfo)
    {
        return (methodInfo.GetBaseDefinition() != methodInfo);
    }
}

7
这个实现会针对继承的方法返回 true -- 参见NUnit测试代码m.GetBaseDefinition().DeclaringType != m.DeclaringType 更可靠。 - ESV
代码存在语法错误,关键字"this"代表什么? - AZ_
如果ReflectedType不同,MethodInfo可能会有所不同。 - Denis535
2
@AZ_ this 是签名的一部分,使得 IsOverride 方法成为一个静态方法。 - LosManos

6
一个简单的解决方案,也适用于受保护的成员和属性,如下所示:
var isDerived = typeof(Test1 ).GetMember("TestMe", 
               BindingFlags.NonPublic 
             | BindingFlags.Instance 
             | BindingFlags.DeclaredOnly).Length == 0;

这是我在这里的回答的转载,该回答参考了这个问题。


2

根据这个答案,在不知道确切的派生或基类型的情况下,还可以通过测试MethodAttributes.NewSlot属性来检查虚方法是否被覆盖。

public static bool HasOverride(this MethodInfo method)
{
    return (method.Attributes & MethodAttributes.Virtual) != 0 &&
           (method.Attributes & MethodAttributes.NewSlot) == 0;
}

与另一个扩展方法一起使用

private const BindingFlags Flags = BindingFlags.NonPublic |
    BindingFlags.Public | BindingFlags.Instance;

public static bool HasOverride(this Type type, string name, params Type[] argTypes)
{
    MethodInfo method = type.GetMethod(name, Flags, null, CallingConventions.HasThis,
        argTypes, new ParameterModifier[0]);
    return method != null && method.HasOverride();
}

您可以简单地调用以下内容。
bool hasOverride = GetType().HasOverride(nameof(MyMethod), typeof(Param1Type),
    typeof(Param2Type), ...);

检查派生类是否覆盖了 MyMethod 方法。

就我目前的测试来看,似乎可以正常工作(在我的机器上™)。


1
注意,我认为您误解了NewSlot标志的含义。对于默认参与传统虚拟重写机制的“普通”虚拟/抽象方法,它不会被断言。相反,NewSlot是指(特殊的?).NET能力,可以有选择性地取消多态链。在**C#**中对应于NewSlot的概念是new关键字,它可以应用于派生类中的(否则-)虚拟方法,以便将其及其后续派生与基本方法多态性分离。 - Glenn Slayden
@GlennSlayden 感谢您指出这一点。根据参考答案中的解释,对于基本虚拟方法和隐藏在基类中实现的方法,NewSlot标志(以及Virtual标志)都会被设置,因为在这两种情况下,这些方法将在类的vtable中获得一个新的插槽。另一方面,覆盖方法将不会在vtable中获得新的插槽,因此只设置Virtual标志。无论如何,正如您已经说过的那样,ESV的答案实际上是这里问题的最佳和最直接的解决方案。 - swalex
1
如果基方法上也断言了NewSlot标志,那么我会纠正自己的观点,谢谢。我猜测OP的问题陈述在是否应将NewSlot(C#中的new关键字)派生方法分析为“HasOverride”方面存在不充分的规定。 - Glenn Slayden

2

一种在某些非平凡情况下也适用的方法:

public bool Overrides(MethodInfo baseMethod, Type type)
{
    if(baseMethod==null)
      throw new ArgumentNullException("baseMethod");
    if(type==null)
      throw new ArgumentNullException("type");
    if(!type.IsSubclassOf(baseMethod.ReflectedType))
        throw new ArgumentException(string.Format("Type must be subtype of {0}",baseMethod.DeclaringType));
    while(type!=baseMethod.ReflectedType)
    {
        var methods=type.GetMethods(BindingFlags.Instance|
                                    BindingFlags.DeclaredOnly|
                                    BindingFlags.Public|
                                    BindingFlags.NonPublic);
        if(methods.Any(m=>m.GetBaseDefinition()==baseMethod))
            return true;
        type=type.BaseType;
    }
    return false;
}

还有一些不太好看的测试:

public bool OverridesObjectEquals(Type type)
{
    var baseMethod=typeof(object).GetMethod("Equals", new Type[]{typeof(object)});
    return Overrides(baseMethod,type);
}

void Main()
{
    (OverridesObjectEquals(typeof(List<int>))==false).Dump();
    (OverridesObjectEquals(typeof(string))==true).Dump();
    (OverridesObjectEquals(typeof(Hider))==false).Dump();
    (OverridesObjectEquals(typeof(HiderOverrider))==false).Dump();
    (OverridesObjectEquals(typeof(Overrider))==true).Dump();
    (OverridesObjectEquals(typeof(OverriderHider))==true).Dump();
    (OverridesObjectEquals(typeof(OverriderNothing))==true).Dump();
}

class Hider
{
  public virtual new bool Equals(object o)
    {
      throw new NotSupportedException();
    }
}


class HiderOverrider:Hider
{
  public override bool Equals(object o)
    {
      throw new NotSupportedException();
    }
}

class Overrider
{
  public override bool Equals(object o)
    {
      throw new NotSupportedException();
    }
}


class OverriderHider:Overrider
{
  public new bool Equals(object o)
    {
      throw new NotSupportedException();
    }
}

class OverriderNothing:Overrider
{

}

1

有一种更好、更安全、更快的方法来实现它。

如果您的类实例将拥有长寿命并且需要多次执行IsOverridden检查,则这种技术是有意义的。

为了解决这个问题,我们可以使用缓存和C#委托,比反射快得多!

// Author: Salvatore Previti - 2011.

/// <summary>We need a delegate type to our method to make this technique works.</summary>
delegate int MyMethodDelegate(string parameter);

/// <summary>An enum used to mark cache status for IsOverridden.</summary>
enum OverriddenCacheStatus
{
    Unknown,
    NotOverridden,
    Overridden
}

public class MyClassBase
{
    /// <summary>Cache for IsMyMethodOverridden.</summary>
    private volatile OverriddenCacheStatus pMyMethodOverridden;

    public MyClassBase()
    {
        // Look mom, no overhead in the constructor!
    }

    /// <summary>
    /// Returns true if method MyMethod is overridden; False if not.
    /// We have an overhead the first time this function is called, but the
    /// overhead is a lot less than using reflection alone. After the first time
    /// this function is called, the operation is really fast! Yeah!
    /// This technique works better if IsMyMethodOverridden() should
    /// be called several times on the same object.
    /// </summary>
    public bool IsMyMethodOverridden()
    {
        OverriddenCacheStatus v = this.pMyMethodOverridden;
        switch (v)
        {
            case OverriddenCacheStatus.NotOverridden:
                return false; // Value is cached! Faaast!

            case OverriddenCacheStatus.Overridden:
                return true; // Value is cached! Faaast!
        }

        // We must rebuild cache.
        // We use a delegate: also if this operation allocates a temporary object
        // it is a lot faster than using reflection!

        // Due to "limitations" in C# compiler, we need the type of the delegate!
        MyMethodDelegate md = this.MyMethod;

        if (md.Method.DeclaringType == typeof(MyClassBase))
        {
            this.pMyMethodOverridden = OverriddenCacheStatus.NotOverridden;
            return false;
        }

        this.pMyMethodOverridden = OverriddenCacheStatus.Overridden;
        return true;
    }

    /// <summary>Our overridable method. Can be any kind of visibility.</summary>
    protected virtual int MyMethod(string parameter)
    {
        // Default implementation
        return 1980;
    }

    /// <summary>Demo function that calls our method and print some stuff.</summary>
    public void DemoMethod()
    {
        Console.WriteLine(this.GetType().Name + " result:" + this.MyMethod("x") + " overridden:" + this.IsMyMethodOverridden());
    }
}

public class ClassSecond :
    MyClassBase
{
}

public class COverridden :
    MyClassBase
{
    protected override int MyMethod(string parameter)
    {
        return 2011;
    }
}

class Program
{
    static void Main(string[] args)
    {
        MyClassBase a = new MyClassBase();
        a.DemoMethod();

        a = new ClassSecond();
        a.DemoMethod();

        a = new COverridden();
        a.DemoMethod();

        Console.ReadLine();
    }
}

当您将此程序作为控制台应用程序运行时,它将打印:
MyClassBase result:1980 overridden:False
ClassSecond result:1980 overridden:False
COverridden result:2011 overridden:True

已在Visual Studio 2010、C# 4.0下进行测试。 应该也能在之前的版本上运行,但在低于3.0的C#版本上可能会稍微慢一些,因为新版本中委托的优化处理,欢迎对此进行测试 :) 但是使用反射仍然比这种方法慢!


你的缓存策略相当次优。我宁愿使用静态字典,这样你就可以得到一个通用的帮助方法。ConditionalWeakTable<Type,Dictionary<MethodInfo,bool>>似乎是一个不错的选择。当然,它的问题与Rex的回答一样。 - CodesInChaos
从我的角度来看,如果您只有少量实例并且对象的寿命非常长,则不是次优的。如果实例的寿命很短,那么就是次优的,就像我在答案中所说的那样。其次,如果添加其他参数的方法,它也可以工作,因为我们使用委托来完成这个技巧。使用字典不是线程安全的,您需要至少一个并发字典,并且当然,在并发或锁定字典中查找比在字段中查找要慢。实际上,这完全取决于要求。 - Salvatore Previti

1
    public static bool HasOverridingMethod(this Type type, MethodInfo baseMethod) {
        return type.GetOverridingMethod( baseMethod ) != null;
    }
    public static MethodInfo GetOverridingMethod(this Type type, MethodInfo baseMethod) {
        var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod;
        return type.GetMethods( flags ).FirstOrDefault( i => baseMethod.IsBaseMethodOf( i ) );
    }
    private static bool IsBaseMethodOf(this MethodInfo baseMethod, MethodInfo method) {
        return baseMethod.DeclaringType != method.DeclaringType && baseMethod == method.GetBaseDefinition();
    }

0

基于thisthis答案的简单一行代码:

typeof(T).GetMember(nameof("Method")).OfType<MethodInfo>()
                .Where(m => m.GetBaseDefinition().DeclaringType != m.DeclaringType);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接