Определите, был ли метод переопределен с помощью Reflection (C #) - PullRequest
55 голосов
/ 29 мая 2010

Допустим, у меня есть базовый класс TestBase, в котором я определяю виртуальный метод TestMe ()

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

Теперь я наследую этот класс:

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

Теперь, используя Reflection, мне нужно выяснить, был ли метод TestMe переопределен в дочернем классе - возможно ли это?

Для чего мне это нужно - я пишу визуализатор конструктора для типа «объект», чтобы показать всю иерархию наследования и также показать, какие виртуальные методы были переопределены на каком уровне.

Ответы [ 8 ]

63 голосов
/ 29 мая 2010

Учитывая тип Test1, вы можете определить, имеет ли он собственную реализацию объявление TestMe:

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

Если объявление получено из базового типа, оно будет оцениваться как ложное.

Обратите внимание, что поскольку это декларация тестирования, а не реализация true, будет возвращать true, если Test1 также является абстрактным, а TestMe абстрактным, поскольку Test1 будет иметь собственное объявление. Если вы хотите исключить этот случай, добавьте && !GetMethod("TestMe").IsAbstract

21 голосов
/ 05 апреля 2012

Как указал @CiprianBortos, принятый ответ не является полным и приведет к неприятной ошибке в вашем коде, если вы будете использовать его как есть.

В его комментарии содержится волшебное решение GetBaseDefinition(), но нет необходимости проверять DeclaringType, если вы хотите проверить IsOverride общего назначения (что, я думаю, было целью этого вопроса), просто methodInfo.GetBaseDefinition() != methodInfo .

Или, предоставленный в качестве метода расширения на MethodInfo, я думаю, что это поможет:

public static class MethodInfoUtil
{
    public static bool IsOverride(this MethodInfo methodInfo)
    {
        return (methodInfo.GetBaseDefinition() != methodInfo);
    }
}
18 голосов
/ 14 мая 2013

Мне не удалось заставить предложенное Кеном Беккетом решение работать. Вот что я остановился на:

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

Есть тесты в суть .

6 голосов
/ 30 августа 2011

Простое решение, которое также будет работать для защищенного элемента и свойств, выглядит следующим образом:

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

Это репост моего ответа здесь , который, в свою очередь, содержал ссылки на этот вопрос.

2 голосов
/ 02 мая 2016

Согласно этому ответу также может быть простой способ проверить, был ли переопределен виртуальный метод, без знания точного производного или базового типа с помощью теста для атрибута 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 в производном классе.

Насколько я это проверил, казалось, что он работает нормально (на моей машине ™).

2 голосов
/ 27 февраля 2012

Метод, который также работает в некоторых нетривиальных случаях:

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
{

}
1 голос
/ 08 мая 2019
    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();
    }
0 голосов
/ 14 августа 2011

Есть лучший, безопасный и быстрый способ сделать это. Этот метод имеет смысл, если ваш экземпляр класса будет иметь длительный срок службы, и проверка 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 # менее 3.0 из-за оптимизации делегатов в новых выпусках, тесты по этому поводу были бы оценены :) Однако это будет все же быстрее, чем использование отражения!

...