方法默认是“虚拟的”还是“非虚拟的”?

4
根据这个类似的StackOverflow问题和其他文章,C#方法默认情况下是“非虚拟的”,这意味着您不能在派生类中重写它们。
如果这是真的,那么你可以解释一下,在下面的示例中,我如何能够在Child类中实现LastName属性,而没有将该属性标记为基类中的“virtual”?Child.LastName属性是否隐藏了(VB“Shadows”)基类中的同一个属性?如果是这样,为什么Child.LastName属性中不使用“new”关键字来指示这一点?
这个测试示例似乎向我表明方法和默认情况下是虚拟的,在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来查看调用哪个实现。 - millimoose
那段代码应该在FirstNameLastName和两个Go方法上警告你使用new - shf301
2
你所忽略的是没有读取编译器的输出,它告诉你忘记说“new”了。阅读编译器的输出;它通常会告诉你一些有趣的东西。 - Eric Lippert
4个回答

4

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


3

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

子类隐藏了基类的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#编程面试问题 :)

2

对多态性的良好理解将有助于澄清这一点:

多态性(C#编程指南)

使用新成员隐藏基类成员


如果您希望派生成员与基类中的成员具有相同的名称,但不希望它参与虚拟调用,则可以使用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修饰符的override来停止虚继承。这需要在类成员声明中将sealed关键字放在override关键字之前。以下代码提供了一个例子:

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

在前面的例子中,方法DoWork不再对任何派生自C类的类是虚拟的。即使将它们强制转换为类型B或类型A,对于C的实例仍然是虚拟的。使用new关键字,派生类可以替换封闭方法,如下面的示例所示:
public class D : C
{
    public new void DoWork() { }
}

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

1

好的,你说得对。如果不是虚拟的,它就会被隐藏。

new 关键字会破坏继承层次结构链中的虚拟重写。

一个简单的例子可供参考:C#中的多态、方法隐藏和重写


我本可以将任何一个回答标记为答案,但我真的很喜欢这个例子。它回答了我的确切问题,大家都投了赞成票。感谢所有人。 - Chad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接