19

可能重复:
为什么 C# 默认将方法实现为非虚拟方法?

我说的主要是 C#、.NET 3.5,但一般想知道不考虑所有“虚拟”的好处是什么——也就是说,在子类的实例中调用的方法总是执行最子版本那个方法。在 C# 中,如果父方法未使用“virtual”修饰符标记,则情况并非如此。例子:

public class Parent
{
    public void NonVirtual() { Console.WriteLine("Non-Virtual Parent"); }
    public virtual void Virtual(){ Console.WriteLine("Virtual Parent"); }
}

public class Child : Parent
{
    public new void NonVirtual() { Console.WriteLine("Non-Virtual Child"); }
    public override void Virtual() { Console.WriteLine("Virtual Child"); }
}

public class Program
{
    public static void Main(string[] args)
    {
        Child child = new Child();
        Parent parent = new Child();
        var anon = new Child();

        child.NonVirtual();           // => Child
        parent.NonVirtual();          // => Parent
        anon.NonVirtual();            // => Child
        ((Parent)child).NonVirtual(); // => Parent

        child.Virtual();              // => Child
        parent.Virtual();             // => Child
        anon.Virtual();               // => Child
        ((Parent)child).Virtual();    // => Child
    }
}

上面观察到的非虚拟行为究竟有什么好处?我唯一能想到的是“如果 Parent 的作者不希望他的方法是虚拟的怎么办?” 但后来我意识到我想不出一个好的用例。有人可能会争辩说,该类的行为取决于非虚拟方法的运行方式——但在我看来,这似乎是一些糟糕的封装,或者该方法应该被密封。

按照同样的思路,“隐藏”似乎通常是个坏主意。毕竟,如果创建了 Child 对象和方法,似乎是出于特定原因覆盖 Parent 的。而且,如果 Child 实现(并隐藏了父级)NonVirtual(),则很容易无法获得许多人可能认为的调用 Child::NonVirtual() 的“预期”行为。(我说“预期”是因为有时很容易不注意到“隐藏”正在发生)。

那么,不让一切都有“虚拟”行为有什么好处呢?如果很容易出现意外行为,那么隐藏非虚拟父级的好用例是什么?

如果有人好奇我为什么提出这个问题——我最近正在研究 Castle Projects DynamicProxy 库。使用它的一个主要障碍是您想要代理的任何方法(或属性)都必须是虚拟的。对于开发人员来说,这并不总是一种选择(如果我们无法控制源代码)。更不用说 DynamicProxy 的目的是避免代理类与您尝试使用代理实现的任何行为之间的耦合(例如 Logging,或者可能是 Memoization 实现)。并且通过强制虚拟方法来实现这一点,所实现的是 DynamicProxy 与它所代理的所有类的非常薄但钝的耦合 - 想象一下,您有大量标记为虚拟的方法,即使它们从未被继承和覆盖,

无论如何,那里的挫败感让我想知道非虚拟的好处是什么,当似乎让一切虚拟化可能更清楚(我想是 IMO)并且也许(?)有更多的好处。

编辑:标记为社区 wiki,因为这似乎是一个可能有主观答案的问题

4

6 回答 6

9

因为你不希望人们覆盖你没有设计类的方法。确保重写方法或什至从类派生的安全性需要付出很大的努力。virtual如果你没有考虑到可能发生的事情,让它变得更安全。

于 2009-06-30T21:44:33.240 回答
7

Eric Lippert 在这里介绍了方法隐藏

于 2009-06-30T21:36:03.820 回答
3

在许多情况下,给定方法具有特定行为对于类的正常运行至关重要。如果该方法在继承的类中被重写,则无法保证该方法将正确实现预期的行为。如果您的类是专门为继承而设计的并且将支持具有不同实现的方法,则应仅将方法标记为虚拟。设计继承并不容易,在很多情况下,错误地覆盖方法会破坏类的内部行为

于 2009-06-30T21:38:18.027 回答
2

简单:类中的全部要点是封装某种抽象。例如,我们想要一个表现为文本字符串的对象。

现在,如果一切都是虚拟的,我将能够做到这一点:

class MessedUpString : String{
   override void Trim() { throw new Exception(); }
}

然后将其传递给一些需要字符串的函数。当他们试图修剪那根绳子时,它就会爆炸。

该字符串不再表现为字符串。这怎么可能是一件好事?

如果一切都是虚拟的,那么你将很难强制执行类不变量。您允许破坏类抽象。

默认情况下,一个类应该封装它期望遵循的规则和行为。原则上,您使虚拟化的所有内容都是可扩展性挂钩,可以更改功能以执行任何操作。这只在少数情况下才有意义,当我们的行为实际上是用户定义的。

类有用的原因是它们允许我们忽略实现细节。我们可以简单地说“这是一个字符串对象,我知道它会表现得像一个字符串。我知道它永远不会违反任何这些保证”。如果无法维持该保证,则该类将毫无用处。您不妨将所有数据成员公开,并将成员方法移到类外。

你知道里氏替换原则吗?在任何需要基类 B 的对象的地方,您都应该能够传递派生类 D 的对象。这是面向对象编程的最基本规则之一。我们需要知道,当我们将派生类向上转换到基类并将它们传递给期望基类的函数时,派生类仍然可以工作。这意味着我们必须使某些行为固定不变。

于 2009-06-30T21:42:59.717 回答
0

非虚拟方法的一个关键好处是它可以在编译时绑定。那就是编译器可以确定在代码中使用方法时要调用哪个实际方法。

如果该方法被声明为虚拟方法,则在编译时无法知道要调用的实际方法,因为该引用实际上可能指向已覆盖它的子类型。因此,当需要解析调用的实际方法时,运行时会产生少量开销。

于 2009-06-30T21:34:32.107 回答
0

在框架中,可以调用非虚拟成员并具有一系列预期输出,如果该方法是虚拟的,则该方法的结果可能是未经测试的预期结果。允许方法是非虚拟的给框架动作带来预期的结果。

于 2009-06-30T21:40:19.553 回答