2010-05-28 39 views
45

说我有一个基类TestBase其中I定义虚拟方法TESTME()中重写检测一个方法是使用反射(C#)

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

现在我继承这个类:

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

现在,使用Reflection,我需要查找TestMe方法是否在子类中被覆盖 - 是否有可能?

我需要什么 - 我正在写一个类型为“object”的设计器可视化工具来显示继承的整个层次结构,并显示在哪个级别重写哪些虚拟方法。

+0

我不知道如何,但这样的事情必须是可能的。有一个名为“RedGate Reflector”的优秀工具,它将显示库中某个方法的逻辑。 – 2010-05-28 20:53:02

回答

52

鉴于类型Test1,可以判断它是否有自己的 实施 声明TestMe

typeof(Test1).GetMethod("TestMe").DeclaringType == typeof(Test1) 

如果声明从基类来了,这将评估假。

注意,因为这是测试声明,没有真正实现,这将如果Test1也是抽象和TestMe是抽象的返回true,因为Test1将有自己的声明。如果你想排除这种情况下,添加&& !GetMethod("TestMe").IsAbstract

+0

感谢雷克斯,那就是我一直在寻找的! – Andrey 2010-05-29 04:52:06

+12

该解决方案不完整。它不包括Test1声明一个名称相同但参数不同的方法的情况。如果上面的测试评估为true,那么您只知道Test1具有TestMe名称的方法,但您不知道它是否为覆盖。您还需要使用GetBaseDefinition()方法。如果这个调用返回一个具有DeclaringType == typeof(TestBase)的MethodInfo对象,那么只有这样你才能确定你有覆盖。 – 2011-05-08 04:43:50

+2

@Ciprian这不是一个完整的代码解决方案,只是解释在哪里可以找到反射的相关部分以便在关闭时拉出。 – 2011-05-08 16:02:18

0

有一个更好,更安全,更快的方式来做到这一点。 如果您的类实例将具有较长的寿命并且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。 应该也适用于以前的版本,但是由于在新版本中对代表进行了优化,所以它在C#上的速度可能会稍微慢一点,所以关于这方面的测试将不胜感激:) 但是它仍然比使用反射更快!

+0

您的高速缓存策略相当不理想。我宁愿使用静态字典,所以你可以得到一个通用的帮助方法。 'ConditionalWeakTable >'好像是个不错的选择。当然,它和雷克斯的答案一样,都被打破了。 – CodesInChaos 2012-02-27 15:11:45

+0

从我的角度来看,如果你有少量的实例并且对象的寿命很长,那么它并不是最理想的。正如我在答案中所说的那样,如果实例的寿命短,它就是小数。其次,如果你添加一个带有其他参数的方法,它就可以工作,因为我们使用一个委托来完成这个技巧。使用字典不是线程安全的,至少需要一个并发字典,当然,查看并发字典或锁定字典比查看字段要慢。这一切都取决于实际需求。 – 2012-02-28 10:28:25

+0

不错。我使用这种方法,运作良好。 – leegod 2017-05-11 09:34:09

4

一个简单的解决方案还将为保护成员和属性的作用如下:

var isDerived = typeof(Test1).GetMember("TestMe", 
       BindingFlags.NonPublic 
      | BindingFlags.Instance 
      | BindingFlags.DeclaredOnly).Length == 0; 

这就是我的回答here,而这又对这个问题做参考的重新发布。

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 
{ 

} 
19

由于@CiprianBortos指出,接受的答案是不完整的,并且将导致如果您按原样使用它,则会在代码中出现令人讨厌的错误。

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

或者,作为MethodInfo扩展方法提供的,我认为这将这样的伎俩:

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

此实现对于继承的方法返回true - 请参见[NUnit测试要点](https://gist.github.com/EdVinyard/5571213)。 'm.GetBaseDefinition()。DeclaringType!= m.DeclaringType'效果更好。 – ESV 2013-05-13 20:29:01

10

我无法得到Ken Beckett's proposed solution工作。这就是我所定下的:

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

the gist有测试。

+0

工程就像一个魅力。非常感谢! 只是有关获取MethodInfo实例的评论。我首先犯了错误: 'typeof(SomeType).GetMethod(someFunctionName)' 使用此MethodInfo实例IsOverride不起作用。您需要这样做: 'someTypeInstance.GetType()。GetMethod(someFunctionName)' 这当然是完全合乎逻辑的,但仍然有点微妙。显然,在调用GetType()时,对实例的引用保存在返回的Type对象中。 – 2017-04-20 14:07:13

2

根据this answer那里也可以是一个简单的方法来检查一个虚拟方法重写而无需使用用于测试的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是否在派生类中被覆盖。

据我测试这个,它似乎工作正常(在我的机器上)。