我主要是谈论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
}
}
上面观察到的非虚拟行为的确切好处是什么?我唯一想到的是“如果Parent的作者不希望他的方法是虚拟的怎么办?”但是后来我意识到我无法想到一个好的用例。可能有人争辩说,类的行为取决于非虚拟方法的运行方式-但是在我看来,这种封装方式很糟糕,或者应该密封该方法。
沿着这些相同的路线,似乎'隐藏'通常不是一个好主意。毕竟,如果创建了Child对象和方法,则似乎是出于特定原因覆盖了Parent的缘故。而且,如果Child实现(并隐藏了父级)NonVirtual(),则not轻松获得许多人认为调用Child :: NonVirtual()的“预期”行为非常容易。 (我之所以说“期望的”,是因为有时不容易注意到“隐藏”正在发生)。
所以,不允许所有事物都具有“虚拟”行为有什么好处?如果很容易发生意外行为,隐藏非虚拟父母的好用例是什么?
[如果有人对我为什么提出这个问题感到好奇-我最近正在研究Castle Projects DynamicProxy库。使用它的一个主要障碍是,您要代理的任何方法(或属性)都必须是虚拟的。对于开发人员来说,这并非总是一种选择(如果我们无法控制源代码)。更不用说DynamicProxy的目的是避免代理类与您试图通过代理实现的任何行为(例如日志记录或Memoization实现)之间的耦合。通过强制使用虚拟方法来完成此任务,虽然实现的方法非常薄,但是将DynamicProxy与它正在代理的所有类的耦合变得钝化-想象一下,您有很多标记为虚拟的方法,即使它们从未被继承和覆盖,因此还有其他方法看代码的开发人员可能会想:“为什么这些甚至是虚拟的?让我们将它们改回来”。
无论如何,那里的挫败感使我想知道非虚拟的好处是什么,似乎所有虚拟的东西似乎都更加清晰了(我想是国际海事组织(IMO)),也许(?)有更多的好处。
编辑:标记为社区Wiki,因为它似乎是一个可能具有主观答案的问题
因为您不希望人们重写您没有为其设计类的方法。要付出很大的努力才能确保可以安全地重写方法甚至从类派生。如果您尚未考虑可能发生的情况,将其设置为非virtual
更为安全。
[在许多情况下,类的正常运行对于给定方法具有特定行为至关重要。如果该方法在继承的类中被重写,则不能保证该方法将正确实现预期的行为。仅当您的类专门为继承而设计并且将支持具有不同实现的方法时,才应将方法标记为虚拟。为继承进行设计并不容易,在很多情况下,错误地重写方法会破坏类的内部行为
简单:类的全部要点是封装某种抽象。例如,我们想要一个表现为文本字符串的对象。
现在,如果所有内容都是虚拟的,我将能够执行此操作:
class MessedUpString : String{
override void Trim() { throw new Exception(); }
}
然后将其传递给需要字符串的某些函数。当他们尝试修剪那根弦时,它就会爆炸。
该字符串不再behaves作为字符串。 ever怎么一件好事?
如果将所有内容虚拟化,则将很难强制执行类不变式。您允许破坏类的抽象。
默认情况下,类应封装预期遵循的规则和行为。原则上,您使虚拟化的所有内容都是可扩展性的钩子,该函数可以更改为anything。只有在少数情况下,当我们的行为实际上是用户定义的时,这才有意义。
原因类很有用,因为它们使我们可以忽略实现细节。我们可以简单地说:“这是一个字符串对象,我知道它将表现为字符串。我知道它绝不会违反任何这些保证”。如果不能保证该保证,则该类是无用的。您也可以只公开所有数据成员,然后将成员方法移到类之外。
你知道Liskov Substitution Principle吗?在期望有基类B的对象的任何地方,您都应该能够传递派生类D的对象。这是面向对象编程的最基本规则之一。我们需要知道,当我们将派生类转换为基类并将它们传递给需要基类的函数时,派生类仍然可以工作。这意味着我们必须使某些行为固定且不可更改。
非虚拟方法的一个主要优点是可以在编译时绑定它。也就是说,编译器可以确定在代码中使用某个方法时将调用哪个实际方法。
如果该方法被声明为虚拟方法,则在编译时无法知道要调用的实际方法,因为该引用实际上可能指向已覆盖该方法的子类型。因此,当需要解决实际的调用方法时,运行时的开销很小。
在框架中,可以调用一个非虚拟成员并具有一定范围的预期输出,如果该方法是虚拟的,则该方法的结果可能是未经测试的预期结果。允许非虚拟方法可为框架操作提供预期结果。