2010-05-28 41 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是否在派生類中被覆蓋。

據我測試這個,它似乎工作正常(在我的機器上)。