【问题标题】:Are methods "virtual" by default or "not virtual"?:方法是默认“虚拟”还是“非虚拟”?:
【发布时间】:2013-03-17 19:05:52
【问题描述】:

根据this类似的StackOverflow问题和其他文章,C#方法默认是“非虚拟的”,我认为这意味着你不能在派生类中覆盖它们。

如果这是真的,您能否向我解释一下,在下面的示例中,我如何能够在继承自 Base 类的 Child 类中实现属性 LastName 而不将该属性标记为“虚拟”基类? Child.LastName 属性是否“隐藏”(VB“阴影”)基类中的相同属性?如果是这样,为什么 Child.LastName 属性中没有使用“新”关键字来表明这一点?

这个测试示例似乎向我暗示默认情况下方法和 virtual 并且在 LastName 属性的情况下,隐含“覆盖”,但我很确定这不是案子。

我错过了什么?

public class BaseClass
{

    private string _FirstName;
    public virtual string FirstName {
        get { return _FirstName; }
        set { _FirstName = value; }
    }

    private string _LastName;
    public string LastName {
        get { return _LastName; }
        set { _LastName = value; }
    }

    public void Go()
    {
        MessageBox.Show("Going at default speed in Base Class");
    }

    public void Go(int speed)
    {
        MessageBox.Show("Going at " + speed.ToString() + " in Base Class");
    }
}


public class Child : BaseClass
{

    public override string FirstName {
        get { return "Childs First  Name"; }
        set { base.FirstName = value; }
    }

    public string LastName {
        get { return "Child's Last Name"; }
        set { base.LastName = value; }
    }

    public void Go()
    {
        MessageBox.Show("Going in Child Class");
    }

    public void Go(int speed)
    {
        MessageBox.Show("Going at " + speed.ToString() + " in Child Class");
    }

}

【问题讨论】:

  • 我相信Child.LastName 是一个全新的属性,在这种情况下它遮蔽基类,并且被编译器提前绑定。试试((BaseClass) new Child()).LastName 看看调用了哪个实现。
  • 该代码应该在FirstNameLastNameGo 两种方法上向您发出警告,告诉您使用new
  • 你缺少的是你没有阅读编译器的输出,它告诉你你忘了说“新”。读取编译器输出;它通常会告诉你一些有趣的事情。

标签: c# oop


【解决方案1】:

默认情况下,方法在 C# 中不是虚拟的。 Child 类中的 LastName 对 BaseClass 隐藏了 LastName。据我记得,这段代码甚至可以编译,但是编译器会提供警告,告诉应该使用 new 关键字。

【讨论】:

    【解决方案2】:

    默认情况下它们是非虚拟的。

    子类隐藏基类的LastName 属性。

    如果你写:

    BaseClass b = new Child(...);
    Console.WriteLine(b.LastName);
    

    您将看到调用了基本实现。

    当你编译上面的代码时,编译器会警告你。标准做法是将隐藏基地成员的成员标记为new

    public new string LastName {
        get { return "Child's Last Name"; }
        set { base.LastName = value; }
    }
    

    这是一个非常常见的 C# 编程面试问题:)

    【讨论】:

      【解决方案3】:

      对多态性有一个很好的理解将清楚这一点:

      Polymorphism (C# Programming Guide)

      用新成员隐藏基类成员


      如果您希望派生成员与基类中的成员具有相同的名称,但又不希望它参与虚拟调用,则可以使用 new 关键字。 new 关键字放在被替换的类成员的返回类型之前。以下代码提供了一个示例:

      public class BaseClass
      {
          public void DoWork() { WorkField++; }
          public int WorkField;
          public int WorkProperty
          {
              get { return 0; }
          }
      }
      
      public class DerivedClass : BaseClass
      {
          public new void DoWork() { WorkField++; }
          public new int WorkField;
          public new int WorkProperty
          {
              get { return 0; }
          }
      }
      

      通过将派生类的实例转换为基类的实例,仍然可以从客户端代码访问隐藏的基类成员。例如:

      DerivedClass B = new DerivedClass();
      B.DoWork();  // Calls the new method.
      
      BaseClass A = (BaseClass)B;
      A.DoWork();  // Calls the old method.
      

      防止派生类覆盖虚拟成员


      无论在虚拟成员和最初声明它的类之间声明了多少类,虚拟成员都会无限期地保持虚拟。如果 A 类声明了一个虚拟成员,B 类从 A 派生,C 类从 B 派生,则 C 类继承该虚拟成员,并且可以选择覆盖它,无论 B 类是否为该成员声明了覆盖。以下代码提供了一个示例:

      public class A
      {
          public virtual void DoWork() { }
      }
      public class B : A
      {
          public override void DoWork() { }
      }
      

      派生类可以通过将覆盖声明为密封来停止虚拟继承。这需要在类成员声明中将sealed 关键字放在覆盖关键字之前。以下代码提供了一个示例:

      public class C : B
      {
          public sealed override void DoWork() { }
      }
      

      在前面的例子中,DoWork 方法对于任何从 C 派生的类都不再是虚拟的。它对于 C 的实例仍然是虚拟的,即使它们被强制转换为 B 类型或 A 类型。密封的方法可以用派生代替使用 new 关键字创建类,如下例所示:

      public class D : C
      {
          public new void DoWork() { }
      }
      

      在这种情况下,如果使用 D 类型的变量在 D 上调用 DoWork,则调用新的 DoWork。如果使用 C、B 或 A 类型的变量访问 D 的实例,则对 DoWork 的调用将遵循虚拟继承规则,将这些调用路由到 C 类上的 DoWork 实现。

      【讨论】:

        【解决方案4】:

        嗯,你没看错。如果它不是虚拟的,它就会被隐藏。

        new 关键字阻止了继承层次链中的虚拟覆盖。

        简单示例:Polymorphism, Method Hiding and Overriding in C#

        【讨论】:

        • 我可以将任何回复标记为答案,但我真的很喜欢这个例子。它解决了我的确切问题。每个人都投了赞成票。谢谢大家。
        猜你喜欢
        • 2010-11-01
        • 2013-05-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-10-23
        • 2020-10-21
        相关资源
        最近更新 更多