【问题标题】:Detect if a method was overridden using Reflection (C#)使用反射(C#)检测方法是否被覆盖
【发布时间】:2010-05-28 20:47:28
【问题描述】:

假设我有一个基类 TestBase,我在其中定义了一个虚拟方法 TestMe()

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

现在我继承了这个类:

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

现在,使用反射,我需要查找方法 TestMe 是否已在子类中被覆盖 - 可能吗?

我需要它来做什么 - 我正在为“对象”类型编写一个设计器可视化工具,以显示整个继承层次结构,并显示哪些虚拟方法在哪个级别被覆盖。

【问题讨论】:

  • 我不知道具体怎么做,但是这样的事情一定是可能的。有一个很棒的工具叫做“RedGate Reflector”,它可以显示库中方法的逻辑。

标签: c# reflection overriding


【解决方案1】:

给定类型Test1,你可以判断它是否有自己的implementation声明TestMe

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

如果声明来自基类型,则评估结果为 false。

请注意,由于这是测试声明,而不是真正的实现,如果Test1 也是抽象的并且TestMe 是抽象的,则此返回true,因为Test1 将有自己的声明。如果要排除这种情况,请添加&& !GetMethod("TestMe").IsAbstract

【讨论】:

  • 此解决方案不完整。它不包括 Test1 声明具有相同名称但不同参数的方法的情况。如果上面的测试评估为真,你只知道 Test1 有一个名为 TestMe 的方法,但你不知道它是否是覆盖。您还需要使用 GetBaseDefinition() 方法。如果此调用返回具有 DeclaringType == typeof(TestBase) 的 MethodInfo 对象,则只有这样您才能确定您有一个覆盖。
  • @Ciprian 这不是一个完整的代码解决方案,只是解释在哪里可以找到反射的相关部分,如果关闭。
  • 是的,我做到了,它不正确/非常有限,从你的代码到一个可行的解决方案是相当多的工作。您甚至没有提到像 GetBaseDefinition() 这样的基本 API,以及与方法隐藏或不同方法签名相关的问题。
  • 如果你想检查一个抽象基类,如果派生类覆盖了一个方法,你可以用 this.GetType 来检查:this.GetType().GetMethod("MethodName").DeclaringType == this.GetType()
【解决方案2】:

我无法让Ken Beckett's proposed solution 工作。这是我决定的:

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

the gist中有测试。

【讨论】:

  • 像魅力一样工作。非常感谢!只是关于获取 MethodInfo 实例的评论。我首先犯了一个错误以通过它:typeof(SomeType).GetMethod(someFunctionName) 使用此 MethodInfo 实例 IsOverride 不起作用。您需要改为:someTypeInstance.GetType().GetMethod(someFunctionName) 这当然是完全合乎逻辑的,但仍然有些微妙。显然,在调用 GetType() 时,对实例的引用保存在返回的 Type 对象中。
  • @DimitriC.我正在使用 Assembly.Load([myAssembleyNameHere]).GetTypes() 并且它工作得非常好..应该被标记为接受的解决方案。
  • @DimitriC。不,在此处显示的技术中,对实例的引用不会以任何方式“保留在返回的 Type 对象中;”实际上恰恰相反:每个运行时 instance 都知道它的实际Type,您可以通过GetType() 获得它(准确地说,运行时类型实例的TypeRuntimeType,从 Type 派生的类型)。好消息是,尽管您的解释中出现了一些小问题,但您提供的代码在技术上是获得真正 100% 正确行为的唯一方法,因此它是此页面上最重要的宝石之一...
  • ...因为此页面上的大多数示例仅考虑静态类型,例如typeof(SomeType),因此对于确实覆盖该方法的 SomeType 派生类的任何/所有实例,它们将失败,即使 SomeType 可能不会。由于任何SomeType 派生的实例都可能有权显示,因此您需要在每次调用时通过GetType() 独立检查每个运行时实例。不这样做可能是导致您最初提到的问题的原因。
  • 像魅力一样工作。这应该是公认的答案。
【解决方案3】:

正如@CiprianBortos 所指出的,接受的答案并不完整,如果按原样使用它会导致代码中出现严重的错误。

他的评论提供了神奇的解决方案GetBaseDefinition(),但如果你想要一个通用的IsOverride 检查(我认为这是这个问题的重点),就不需要检查DeclaringType,只需methodInfo.GetBaseDefinition() != methodInfo .

或者,作为MethodInfo 上的扩展方法提供,我认为这样可以解决问题:

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

【讨论】:

  • 这个实现对于继承的方法返回 true -- 见NUnit test gistm.GetBaseDefinition().DeclaringType != m.DeclaringType 效果更好。
  • 代码有语法错误,“this”关键字代表什么?
  • 如果 ReflectedType 不同,MethodInfo 可能会不同。
  • @AZ_ this 是使IsOverride 方法成为静态方法的签名的一部分。
【解决方案4】:

同样适用于受保护成员和属性的简单解决方案如下:

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

这是对我的回答 here 的转贴,该回答又引用了这个问题。

【讨论】:

    【解决方案5】:

    一种在一些不平凡的情况下也有效的方法:

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

    【讨论】:

      【解决方案6】:

      根据this answer,还可以使用一种简单的方法来检查虚拟方法是否被覆盖,而无需使用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 是否在派生类中被覆盖。

      据我测试,它似乎工作正常(在我的机器上)。

      【讨论】:

      • 注意这里,我想你误解了NewSlot 标志的含义。它为“正常”虚拟/抽象方法断言,默认情况下,它们参与传统的虚拟覆盖机制。相反,NewSlot 指的是(特殊的?).NET 选择性取消多态链的能力。 C# 中与NewSlot 对应的概念是new 关键字,它可以应用于派生类中的(否则)虚拟方法,以便将其分离,以及所有进一步的派生,来自基本方法多态性。
      • @GlennSlayden 感谢您指出这一点。正如我所理解的引用答案中的解释,NewSlot 标志设置(连同Virtual 标志)为基虚拟方法和隐藏基类中的实现的方法,因为在这两种情况下,方法将在类的 vtable 中获得一个新插槽。另一方面,覆盖方法不会在 vtable 中获得新插槽,因此仅设置了 Virtual 标志。无论如何,正如您已经说过的,ESV 的答案实际上是这里问题的最佳和最直接的解决方案。
      • 如果NewSlot 标志在基本方法上也被断言,我会更正,谢谢。而且我猜 OPs 问题陈述没有明确说明是否应该将 NewSlot(C# 中的 new 关键字)派生方法分析为“HasOverride”。
      【解决方案7】:
          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();
          }
      

      【讨论】:

        【解决方案8】:

        有一种更好、更安全、更快捷的方法来做到这一点。 如果您的类实例的生命周期很长并且必须执行多次 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# 上它可能会慢一些,对此进行测试将不胜感激:) 但是它仍然比使用反射更快!

        【讨论】:

        • 您的缓存策略并不理想。我宁愿使用静态字典,这样你就可以获得一个通用的辅助方法。 ConditionalWeakTable&lt;Type,Dictionary&lt;MethodInfo,bool&gt;&gt; 似乎是个不错的选择。当然,它的破坏方式与雷克斯的回答相同。
        • 好吧,从我的角度来看,如果您有少量实例并且对象的寿命很长,这并不是次优的。正如我在答案中所说,如果实例的寿命很短,那么它是次优的。其次,如果你添加一个带有其他参数的方法,它会起作用,因为我们使用委托来完成这个技巧。使用字典不是线程安全的,您至少需要一个并发字典,当然,查看并发或锁定字典比查看字段要慢。这一切都取决于实际的要求。
        猜你喜欢
        • 2012-03-29
        • 1970-01-01
        • 2011-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多