为什么不把一切都变成“虚拟”?

时间:2009-06-30 21:29:34

标签: c# .net virtual override

  

可能重复:
  Why C# implements methods as non-virtual by default?

我主要讲的是C#,.NET 3.5,但总体上想知道不考虑所有“虚拟”的好处 - 也就是说在子类实例中调用的方法总是执行子进程该方法的最新版本。在C#中,如果父方法未使用“虚拟”修饰符标记,则情况并非如此。例如:

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

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

沿着这些方向,似乎“隐藏”通常是一个坏主意。毕竟,如果创建了一个Child对象和方法,那么它似乎是出于特定原因而覆盖Parent。如果Child实现(并隐藏父项)NonVirtual(),那么很容易获得许多可能认为调用Child :: NonVirtual()的“预期”行为。 (我说“预期”,因为有时很容易注意到“隐藏”正在发生)。

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

如果有人对我为何提出这个问题感到好奇 - 我最近正在检查Castle Projects DynamicProxy库。使用它的一个主要障碍是您要代理的任何方法(或属性)都必须是虚拟的。对于开发人员来说,这并不总是一个选择(如果我们无法控制源)。更不用说DynamicProxy的目的是避免代理类与您尝试使用代理实现的任何行为之间的耦合(例如Logging,或者可能是Memoization实现)。通过强制虚拟方法来实现这一目标,实现这一点的目的非常薄,但是将DynamicProxy与它所代表的所有类的钝化耦合 - 想象一下,你有很多标记为虚拟的方法,即使它们永远不会被继承和覆盖,所以任何其他查看代码的开发人员可能会想知道“为什么这些甚至是虚拟的?让我们改回来”。

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

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

6 个答案:

答案 0 :(得分:9)

因为您不希望人们覆盖您没有为该类设计的方法。要确保覆盖方法或从类派生是安全的,需要付出很大的努力。如果您没有考虑可能发生的情况,那么将其设为非virtual会更安全。

答案 1 :(得分:7)

答案 2 :(得分:3)

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

答案 3 :(得分:2)

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

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

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

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

字符串不再表现为字符串。那永远是一件好事?

如果所有内容都是虚拟的,那么您将很难执行类不变量。你允许类抽象被破坏。

默认情况下,类应该封装预期遵循的规则和行为。你制作虚拟的所有内容原则上都是一个可扩展性钩子,可以将该函数更改为任何。只有在我们拥有实际上由用户定义的行为时,这才有意义。

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

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

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

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

相关问题