2010-05-28 16 views
45

dire che ho un TestBase classe base dove mi definisco un metodo TestMe virtuale()rilevare se un metodo è stato sovrascritto utilizzando Reflection (C#)

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

Ora ho ereditato questa classe:

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

Ora, usando Reflection, ho bisogno di trovare se il metodo TestMe è stato sovrascritto in classe figlio - è possibile?

Quello che mi serve - Sto scrivendo un visualizzatore di designer per tipo "oggetto" per mostrare l'intera gerarchia dell'ereditarietà e anche mostrare quali metodi virtuali sono stati sovrascritti a quale livello.

+0

Non so esattamente come, ma qualcosa del genere deve essere possibile. C'è un eccellente strumento chiamato "RedGate Reflector" che mostrerà la logica di un metodo in una libreria. –

risposta

52

Dato il tipo Test1, è possibile determinare se si dispone di un proprio realizzazione dichiarazione di TestMe:

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

Se la dichiarazione è venuto da un tipo di base, questa valuterà falso.

Nota che, trattandosi di dichiarazione di collaudo, non è vero implementazione, questo sarà ritornare vero se Test1 è anche astratto e TestMe è astratta, poiché Test1 avrebbe una propria dichiarazione. Se si desidera escludere il caso, aggiungere && !GetMethod("TestMe").IsAbstract

+0

Grazie Rex, è esattamente quello che stavo cercando! – Andrey

+12

Questa soluzione non è completa. Non copre il caso in cui Test1 dichiara un metodo con lo stesso nome ma diversi parametri. Se il test sopra riportato è vero, sai solo che Test1 ha un metodo con nome TestMe ma non sai se è un override. È necessario anche utilizzare il metodo GetBaseDefinition(). Se questa chiamata restituisce un oggetto MethodInfo che ha DeclaringType == typeof (TestBase), solo allora puoi sapere con certezza che hai un override. –

+2

@Ciprian questa non è una soluzione di codice completa, solo spiegando dove trovare le parti rilevanti del riflesso da estrarre se disattivate. –

0

Esiste un modo migliore, più sicuro e più veloce per farlo. Questa tecnica ha senso se l'istanza della classe avrà una lunga durata e il controllo IsOverridden deve essere eseguito più volte.

Per risolvere questo problema, è possibile utilizzare un delegato di cache e C#, molto più veloce della riflessione!

// 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(); 
    } 
} 

Quando si esegue questo programma come un'applicazione di console, verrà stampata:

MyClassBase result:1980 overridden:False 
ClassSecond result:1980 overridden:False 
COverridden result:2011 overridden:True 

Testato con Visual Studio 2010, C# 4.0. Dovrebbe funzionare anche su versioni precedenti, ma può essere un po 'più lento su C# meno di 3.0 a causa delle ottimizzazioni ai delegati nelle nuove versioni, i test su questo sarebbero apprezzati :) Tuttavia sarà ancora più veloce rispetto all'utilizzo di riflessione!

+0

La tua strategia di caching è abbastanza sub-ottimale. Preferisco usare un dizionario statico, in modo da poter ottenere un metodo di supporto generale. 'ConditionalWeakTable >' sembra una buona scelta. E naturalmente è rotto come la risposta di Rex. – CodesInChaos

+0

Dal mio punto di vista, non è ottimale se si dispone di un piccolo numero di istanze e se l'oggetto ha una durata molto lunga. È subotpimale se l'istanza ha una vita breve, come ho detto nella risposta. Secondo, funziona se si aggiunge un metodo con altri parametri, dal momento che stiamo usando un delegato per fare il trucco.L'uso di un dizionario non è thread-safe, è necessario almeno un dizionario simultaneo e, naturalmente, cercare in un dizionario concorrente o bloccato è più lento che cercare in un campo. Tutto dipende dai requisiti in realtà. –

+0

buono. Ho usato questo approccio e funziona bene. – leegod

4

Una soluzione semplice, che funziona anche per gli Stati e le proprietà protetta è la seguente:

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

Questo è un repost della mia risposta here, che a sua volta aveva fatto riferimenti a questa domanda.

2

Un metodo che funziona anche in alcuni casi non banali:

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; 
} 

E alcuni test brutto:

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

Come @CiprianBortos sottolineato, la risposta accettata non è completa e porterà a un brutto bug nel tuo codice se lo usi così com'è.

suo commento fornisce la soluzione magica GetBaseDefinition(), ma non c'è bisogno di controllare il DeclaringType se si desidera un general-purpose IsOverride di controllo (che credo fosse il punto di questa domanda), appena methodInfo.GetBaseDefinition() != methodInfo.

Oppure, fornito come un metodo di estensione su MethodInfo, credo che questo farà il trucco:

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

Questa implementazione restituisce true per i metodi ereditati - consultare [Gins dei test NUnit] (https://gist.github.com/EdVinyard/5571213). 'm.GetBaseDefinition(). DeclaringType! = m.DeclaringType' funziona meglio. – ESV

10

mi è stato in grado di ottenere Ken Beckett's proposed solution al lavoro. Ecco quello che ho optato per:

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

Non ci sono prove in the gist.

+0

Funziona come un fascino. Molte grazie! Solo un commento su come ottenere l'istanza MethodInfo. Per prima cosa ho commesso l'errore di farcela: 'typeof (SomeType) .GetMethod (someFunctionName)' Con questa istanza MethodInfo IsOverride non funziona. Devi farlo invece: 'someTypeInstance.GetType(). GetMethod (someFunctionName)' Questo è perfettamente logico, naturalmente, ma ancora un po 'sottile. Apparentemente, quando si chiama GetType(), un riferimento all'istanza viene mantenuto nell'oggetto Type che viene restituito. –

2

Secondo this answer ci potrebbe essere anche un modo semplice per controllare se un metodo virtuale è stato ignorato senza conoscere il tipo di base esatto derivato o utilizzando un test per l'attributo MethodAttributes.NewSlot:

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

Insieme ad un'altra estensione metodo

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(); 
} 

si potrebbe quindi semplicemente chiamare

bool hasOverride = GetType().HasOverride(nameof(MyMethod), typeof(Param1Type), 
    typeof(Param2Type), ...); 

per verificare se MyMethod viene sovrascritto in una classe derivata.

Per quanto ho provato, sembrava funzionare bene (sulla mia macchina ™).

Problemi correlati