web-dev-qa-db-fra.com

Détecter si une méthode a été remplacée à l'aide de Reflection (C #)

Disons que j'ai une classe de base TestBase où je définis une méthode virtuelle TestMe ()

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

Maintenant, j'hérite de cette classe:

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

Maintenant, en utilisant Reflection, je dois trouver si la méthode TestMe a été remplacée dans la classe enfant - est-ce possible? 

Ce dont j'ai besoin - J'écris un visualiseur de concepteur pour le type "objet" afin de montrer toute la hiérarchie de l'héritage et aussi quelles méthodes virtuelles ont été écrasées à quel niveau.

53
Andrey

Étant donné le type Test1, vous pouvez déterminer s'il possède son propre la mise en oeuvre déclaration de TestMe:

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

Si la déclaration provient d'un type de base, cela évaluera false.

Notez que, puisqu'il s'agit d'une déclaration de test et non d'une implémentation vraie, ceci will retournera true si Test1 est également abstrait et que TestMe est abstrait, puisque Test1 aurait sa propre déclaration. Si vous souhaitez exclure ce cas, ajoutez && !GetMethod("TestMe").IsAbstract

59
Rex M

Comme @CiprianBortos l'a souligné, la réponse acceptée n'est pas complète et entraînera un vilain bug dans votre code si vous l'utilisez tel quel. 

Son commentaire fournit la solution magique GetBaseDefinition(), mais il n'est pas nécessaire de cocher la case DeclaringType si vous souhaitez une vérification IsOverride à usage général (ce qui, je pense, était le but de cette question), mais methodInfo.GetBaseDefinition() != methodInfo.

Ou, fourni comme méthode d'extension sur MethodInfo, je pense que cela fera l'affaire:

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

Je n'ai pas réussi à obtenir la solution proposée par Ken Beckett au travail. Voici ce sur quoi je me suis installé:

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

Il existe des tests dans the Gist .

16
ESV

Une solution simple qui fonctionnera également pour les propriétés et les membres protégés est la suivante: 

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

Ceci est un compte-rendu de ma réponse ici , qui à son tour avait fait référence à cette question.

6
Michael Hays

Selon cette réponse , il pourrait également exister un moyen simple de vérifier si une méthode virtuelle a été remplacée sans connaître le type exact dérivé ou de base à l'aide d'un test pour l'attribut MethodAttributes.NewSlot:

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

Avec une autre méthode d'extension

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

vous pouvez alors simplement appeler

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

vérifier si MyMethod est écrasé dans une classe dérivée.

Dans la mesure où j'ai testé cela, cela semblait bien fonctionner (sur ma machine ™).

2
buygrush

Une méthode qui fonctionne également dans certains cas non triviaux:

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

Et quelques tests laids:

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
{

}
2
CodesInChaos
    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();
    }
1
wishmaster35

Il existe un moyen plus efficace, plus sûr et plus rapide de le faire .. Cette technique est utile si votre instance de classe a une longue durée de vie et si la vérification IsOverridden doit être effectuée plusieurs fois.

Pour résoudre ce problème, nous pouvons utiliser un cache et des délégués C #, beaucoup plus rapidement que la réflexion!

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

Lorsque vous exécutez ce programme en tant qu'application console, il imprimera:

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

Testé avec Visual Studio 2010, C # 4.0 . Devrait fonctionner également sur les versions précédentes, mais il peut être un peu plus lent sur C # inférieur à 3.0 en raison d'optimisations pour les délégués dans les nouvelles versions, des tests à ce sujet seraient appréciés :) Cependant, ce sera toujours plus rapide que d'utiliser la réflexion!

0
Salvatore Previti