【问题标题】:Is it possible to tell if a .NET virtual method has been overriden in a derived class?是否可以判断 .NET 虚拟方法是否已在派生类中被覆盖?
【发布时间】:2009-05-08 14:15:29
【问题描述】:

提出这个问题的行为本身就表明我对这个问题的处理方法是不正确的,所以我对直接解决问题的答案以及对我正在做的事情提出更简洁方法的答案感兴趣。

考虑一个基类,它提供一组标准的服务和功能,以及围绕这些的一些结构。通过比喻的方式,让我们考虑以下示例类:

public class ExampleBase
{
  public void Main()
  {
    // Do something
    PreValidate(); // Extensibility point for derived classes
    // Do something else
    PostValidate(); // Extensibility point for derived classes
    // Do something else 
  }

  protected virtual void PreValidate()
  {
  }

  protected virtual void PostValidate()
  {
  }
}

派生类现在可以覆盖这些虚拟方法以提供一些自定义逻辑。

问题来了:基类是否有可能在运行时发现派生类是否冒昧地重写了这些虚拟方法之一,之前调用虚方法?

(如果在调用该方法之后知道这个问题的答案就足够了,那么您也许可以用设置私有标志的方法替换基类中的空方法,这将表示该方法没有被覆盖。但是,如果派生类在其覆盖的实现中调用base.PreValidate(),这可能会被愚弄。)

如果需要这种级别的灵活性,也许最好的解决方案是使用完全不同的可扩展机制?

【问题讨论】:

    标签: .net


    【解决方案1】:

    虽然其他答案中提到了解决此问题的最佳方法,但我回答了问题标题(这将处理 new 问题):

    var isOverridden = 
       new Action(MethodName).Method.DeclaringType != typeof(BaseClass);
    // Replace `Action` with any conforming delegate type.
    

    这个技巧使 CLR 方法调度机制为我们找到MethodInfo,而不是诉诸反射来找到它。

    【讨论】:

    • 偷偷摸摸!不知道这是可能的,但可以让您绕过各种反思和“新”问题。
    【解决方案2】:

    是否可以在运行时判断方法 X 是否被覆盖

    是的,但正如您已经指出的那样,您可能不想这样做。您可以使用 this.GetType() + 反射魔法在派生类中查找覆盖当前基的定义方法。这是非常昂贵和棘手的,因为您必须考虑重载、深层层次结构和其他有趣的通用问题。

    编辑

    有关不使用反射的方法,请参阅 Mehrdad 的回答。

    您应该采取哪些不同的做法?

    你应该把这个方法分成两部分。

    1. 您绝对依赖运行的部分。使其成为非虚拟方法,以便派生类不会干扰它
    2. 可扩展性部分。使其成为从 #1 调用的虚拟受保护方法。

    例如

    protected void PreValidate() {
      .. my validation code
      PreValidateImpl();
    }
    
    protected virtual void PreValidateImpl() {
      // Nothing by default
    }
    

    【讨论】:

      【解决方案3】:

      你可以用接口来模拟这个:

      public interface ISupportPreValidate
      {
          void PreValidate();
      }
      
      public interface ISupportPostValidate
      {  
          void PostValidate();
      }
      
      public class Base
      {
          public void Validate()
          {
              if(this is ISupportPreValidate)
                  ((ISupportPreValidate)this).PreValidate();
      
              // Validation logic
      
              if(this is ISupportPostValidate)
                  ((ISupportPostValidate)this).PostValidate();            
          }
      }
      

      然后在Base派生类中实现所需的接口。

      【讨论】:

        【解决方案4】:

        你可以这样做:

        public abstract class ExampleBase
        {
          public void Main()
          {
            // Do something
            PreValidate(); // Extensibility point for derived classes
            // Do something else
            PostValidate(); // Extensibility point for derived classes
            // Do something else 
          }
        
          protected abstract void PreValidate();
        
          protected abstract void PostValidate();
        
        }
        

        这将强制所有派生类型覆盖方法。您最好不要在执行时检查这种事情 - 使用抽象类型和抽象方法,您允许编译器为您完成提升,以确保您的类型的所有使用者覆盖这些方法。

        如果类型为 abstract 不起作用,那么您总是可以这样做:

        public class ExampleBase
        {
            public void Main()
            {
                // Do something
                PreValidate(); // Extensibility point for derived classes
                // Do something else
                PostValidate(); // Extensibility point for derived classes
                // Do something else 
            }
        
            protected virtual void PreValidate()
            {
                throw new NotImplementedException();
            }
        
            protected virtual void PostValidate()
            {
                throw new NotImplementedException();
            }
        }
        

        但这种方法将任何潜在错误从编译时转移到执行时,最好不惜一切代价避免。

        【讨论】:

          【解决方案5】:

          当然!您将不得不使用反射。

          if (this.GetType().GetMethod("PreValidate").DeclaringType ==
                  typeof(ExampleBase))
          {
              // Not overridden
          }
          else
          {
              // Overridden
          }
          

          请记住,反射有点慢,所以你不想执行它,例如每秒一百万次。

          【讨论】:

          • 这适用于简单的示例,但需要更严格才能普遍使用。您需要同时考虑重载和“新”方法与虚拟覆盖。
          • @JaredPar:在这个特定的例子中,两个方法都是从基类调用的。如果从基类调用具有相同签名的方法,则不会调用“新”方法 - 这就是“新”方法的重点,它们不会引起冲突。
          • @DrJokepu,您不是在检查调用,而是在检查定义。我可以通过在我的基类“private new void PreValidate()”上定义以下方法来使其返回 true。这将导致您的检查返回“true”,而实际上我没有覆盖任何内容。
          【解决方案6】:

          您的 PreValidate() 和 PostValidate() 似乎更适合用作不同类型的扩展模型。将这些作为委托或满足特定接口的完整对象注入的提供者模型可能更适合您希望能够在运行时检查这些服务的状态。

          【讨论】:

            【解决方案7】:

            问自己以下问题:

            1. 我会从 ExampleBase 创建一个对象吗?还是我总是从这个类继承并从继承的类创建一个对象?
            2. 任何派生类都需要 PreValidate 和 PostValidate 方法吗?

            重点是,从您的设计来看,您确实应该创建一个抽象类,使用抽象方法,而不是虚拟方法。另外,从你的描述看来,这也是你想要达到的目的。

            public abstract class ExampleBase
            {
                public void Main()
                {
                    // Do something
                    PreValidate(); // Extensibility point for derived classes
                    // Do something else
                    PostValidate(); // Extensibility point for derived classes
                    // Do something else 
                }
            
                protected abstract void PreValidate();
            
                protected abstract void PostValidate();
            }
            

            通过这种方式,派生类被强制定义这些方法。

            【讨论】:

              【解决方案8】:

              JaredPar 的替代方法很好,但恕我直言,事件在可扩展性方面更清晰。

              protected event ThreadStart OnPreValidate;
              
              protected void PreValidate()
              {
                .. my validation code
                if (null != OnPreValidate) {
                  OnPreValidate();
                }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-12-17
                • 2012-10-26
                • 2020-10-21
                • 1970-01-01
                • 2016-01-24
                相关资源
                最近更新 更多