2010-05-28 19 views
45

decir que tengo un TestBase clase base en la que se define un método TestMe virtuales()detectar si un método fue anulada mediante la reflexión (C#)

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

Ahora heredo esta clase:

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

Ahora, usando Reflection, necesito encontrar si el método TestMe ha sido anulado en la clase infantil - ¿es posible?

Para qué lo necesito - Escribo un visualizador de diseñador para el tipo "objeto" para mostrar toda la jerarquía de herencia y también mostrar qué métodos virtuales se redefinieron en qué nivel.

+0

No sé exactamente cómo, pero algo como esto debe ser posible. Hay una excelente herramienta llamada "RedGate Reflector" que mostrará la lógica de un método en una biblioteca. –

Respuesta

52

Dado el tipo Test1, se puede determinar si tiene su propia aplicación declaración de TestMe:

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

Si la declaración vino de un tipo base, esto va a evaluar falsa.

Tenga en cuenta que ya que esta es la declaración de pruebas, no es cierto aplicación, esta se devolver cierto si Test1 también es abstracto y TestMe es abstracto, ya que Test1 tendría su propia declaración. Si desea excluir ese caso, agregue && !GetMethod("TestMe").IsAbstract

+0

Gracias Rex, ¡eso es exactamente lo que estaba buscando! – Andrey

+12

Esta solución no está completa. No cubre el caso en el que Test1 declara un método con el mismo nombre pero diferentes parámetros. Si la prueba anterior se evalúa como verdadera, solo se sabe que Test1 tiene un método con el nombre TestMe pero no se sabe si es una anulación. También debe usar el método GetBaseDefinition(). Si esta llamada devuelve un objeto MethodInfo que tiene DeclaringType == typeof (TestBase), solo entonces puede estar seguro de que tiene una anulación. –

+2

@Ciprian esto no es una solución de código completa, solo explica dónde encontrar las partes relevantes de reflexión para tirar si está apagado. –

0

Hay una forma mejor, más segura y más rápida de hacerlo. Esta técnica tiene sentido si su instancia de clase va a tener una vida larga y la comprobación IsOverridden se debe realizar varias veces.

Para resolver este problema, podemos usar un caché y C# delegados, ¡mucho más rápido que la reflexión!

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

Al ejecutar este programa como una aplicación de consola, se imprimirá:

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

Probado con Visual Studio 2010, C# 4.0. Debería funcionar también en versiones anteriores, pero puede ser un poco más lento en C# menor que 3.0 debido a las optimizaciones para los delegados en las nuevas versiones, las pruebas sobre esto se agradecerán :) Sin embargo, ¡será aún más rápido que usar reflexión!

+0

Su estrategia de almacenamiento en caché es bastante subóptima. Prefiero usar un diccionario estático, para que pueda obtener un método de ayuda general. 'ConditionalWeakTable >' parece una buena opción. Y, por supuesto, está roto de la misma manera que la respuesta de Rex. – CodesInChaos

+0

Bueno, desde mi punto de vista, no es subóptimo si tiene un pequeño número de instancias y si el objeto tiene una vida muy larga. Es sub-óptimo si la instancia tiene una vida corta, como dije en la respuesta. En segundo lugar, funciona si agrega un método con otros parámetros, ya que estamos utilizando un delegado para hacer el truco.El uso de un diccionario no es seguro para subprocesos, por lo menos necesitaría un diccionario simultáneo y, por supuesto, buscar en un diccionario simultáneo o bloqueado es más lento que buscar en un campo. Todo depende de los requisitos en realidad. –

+0

bien. Usé este enfoque y funciona bien. – leegod

4

Una solución sencilla que también funcionará para miembro protegido y propiedades es como sigue:

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

Este es un repost de mi respuesta here, que a su vez se había hecho referencia a esta pregunta.

2

Un método que también funciona en algunos casos no trivial:

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

Y algunas pruebas feas:

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

Como @CiprianBortos señalaron, la respuesta aceptada no es completa y conducirá a un error desagradable en tu código si lo usas tal cual.

Su comentario proporciona la solución mágica GetBaseDefinition(), pero no hay necesidad de comprobar la DeclaringType si quieres un propósito general IsOverride cheque (que yo creo que es el punto de esta pregunta), justo methodInfo.GetBaseDefinition() != methodInfo.

O, proporcionado como un método de extensión en MethodInfo, creo que esto va a hacer el truco:

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

Esta implementación devuelve true para los métodos heredados; ver [Nunit test gist] (https://gist.github.com/EdVinyard/5571213). 'm.GetBaseDefinition(). DeclaringType! = m.DeclaringType' funciona mejor. – ESV

10

que era incapaz de conseguir Ken Beckett's proposed solution para trabajar. Esto es lo que se establecieron en:

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

Hay pruebas en the gist.

+0

Funciona como un encanto. ¡Muchas gracias! Solo un comentario sobre cómo obtener la instancia de MethodInfo. Primero cometí el error de obtenerlo: 'typeof (SomeType) .GetMethod (someFunctionName)' Con esta instancia de MethodInfo, IsOverride no funciona. Necesitas hacer esto en su lugar: 'someTypeInstance.GetType(). GetMethod (someFunctionName)' Esto es perfectamente lógico, por supuesto, pero aún algo sutil. Aparentemente, al llamar a GetType(), se mantiene una referencia a la instancia en el objeto Type que se devuelve. –

2

Según this answer también podría ser una manera simple para comprobar si un método virtual fue anulada sin saber el tipo de base exacta derivado o el uso de una prueba para el atributo MethodAttributes.NewSlot:

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

Junto con otra extensión método

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

usted podría simplemente llamar

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

para verificar si MyMethod se reemplaza en una clase derivada.

Por lo que he probado esto, parecía funcionar bien (en mi máquina ™).

Cuestiones relacionadas