IT TIP

리플렉션 (C #)을 사용하여 메서드가 재정의되었는지 감지

itqueen 2020. 12. 29. 08:10
반응형

리플렉션 (C #)을 사용하여 메서드가 재정의되었는지 감지


가상 메서드 TestMe ()를 정의하는 기본 클래스 TestBase가 있다고 가정합니다.

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

이제이 클래스를 상속합니다.

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

이제 Reflection을 사용하여 TestMe 메서드가 자식 클래스에서 재정의되었는지 확인해야합니다. 가능합니까?

필요한 것-상속의 전체 계층 구조를 표시하고 어떤 수준에서 어떤 가상 메서드가 재정의되었는지 표시하기 위해 "객체"유형에 대한 디자이너 시각화 도우미를 작성하고 있습니다.


유형이 주어지면 Test1자체 구현 선언 이 있는지 확인할 수 있습니다 TestMe.

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

선언이 기본 유형에서 나온 경우 false로 평가됩니다.

이것은 실제 구현이 아닌 테스트 선언이므로 자체 선언이 있으므로 추상이고 추상 이면 true 반환합니다 . 해당 사례를 제외하려면 다음을 추가하십시오.Test1TestMeTest1&& !GetMethod("TestMe").IsAbstract


@CiprianBortos가 지적했듯이 허용되는 답변은 완전하지 않으며 그대로 사용하면 코드에 불쾌한 버그가 발생합니다.

그의 의견은 마법의 해결책을 제공 GetBaseDefinition()하지만 DeclaringType범용 IsOverride검사 를 원하는지 확인할 필요가 없습니다 (이 질문의 요점이라고 생각합니다) methodInfo.GetBaseDefinition() != methodInfo.

또는에 대한 확장 메서드로 제공되면 MethodInfo이것이 트릭을 할 것이라고 생각합니다.

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

Ken Beckett가 제안한 솔루션 을 작동 시킬 수 없었습니다 . 내가 정한 것은 다음과 같습니다.

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

요점에 테스트가 있습니다 .


보호 된 멤버 및 속성에 대해서도 작동하는 간단한 솔루션은 다음과 같습니다.

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

이것은 여기 에 내 대답 을 다시 게시 한 것이며 차례로이 질문에 대한 참조를 만들었습니다.


사소하지 않은 경우에도 작동하는 방법 :

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
{

}

이 답변 에 따르면 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파생 클래스에서 재정의 되었는지 확인합니다 .

내가 이것을 테스트하는 한, 그것은 잘 작동하는 것 같았습니다 (내 컴퓨터 ™에서).


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

이를 수행하는 더 좋고 안전하며 빠른 방법이 있습니다. 이 기술은 클래스 인스턴스의 수명이 길고 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으로 테스트되었습니다. 이전 버전에서도 작동해야하지만 새 릴리스에서 대리자에 대한 최적화로 인해 3.0 미만의 C #에서 약간 느릴 수 있습니다. 이에 대한 테스트는 감사하겠습니다. :) 그러나 리플렉션을 사용하는 것보다 여전히 빠릅니다!

참조 URL : https://stackoverflow.com/questions/2932421/detect-if-a-method-was-overridden-using-reflection-c

반응형