为什么不让一切都变得“虚拟”呢?
可能的重复:
为什么 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 的目的是避免代理类与您尝试使用代理实现的任何行为(例如日志记录,或者可能是记忆化实现)之间的耦合。 通过强制虚拟方法来完成此操作,所实现的结果是 DynamicProxy 与其代理的所有类之间的耦合非常薄弱,但很钝 - 想象一下,您有大量标记为虚拟的方法,即使它们从未被继承和覆盖,因此任何其他方法查看代码的开发人员可能会想“为什么这些是虚拟的?让我们把它们改回来”。
不管怎样,那里的挫败感让我想知道非虚拟的好处是什么,当一切虚拟化似乎可能更清楚(我想是我认为的)并且也许(?)有更多的好处。
编辑:标记为社区维基,因为这似乎是一个可能有主观答案的问题
Possible Duplicate:
Why C# implements methods as non-virtual by default?
I'm speaking primarily about C#, .NET 3.5, but wonder in general what the benefits are of not considering everything "virtual" - which is to say that a method called in an instance of a child class always executes the child-most version of that method. In C#, this is not the case if the parent method is not labeled with the "virtual" modifier. Example:
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
}
}
What exactly are the benefits of the non-virtual behavior observed above? The only thing I could think of was "What if the author of Parent doesn't want his method to be virtual?" but then I realized I couldn't think of a good use case for that. One might argue that the behavior of the class is dependent on how a non-virtual method operates - but then that seems to me there is some poor encapsulation going on, or that the method should be sealed.
Along these same lines, it seems like 'hiding' is normally a bad idea. After all, if a Child object and methods were created, it seems that it was done so for a specific reason to override the Parent. And if Child implements (and hides the parents) NonVirtual(), it is super easy to not get the what many might consider "expected" behavior of calling Child::NonVirtual(). (I say "expected" because it is sometimes easy to not notice 'hiding' is happening).
So, what are the benefits of not allowing everything to have "virtual" behavior? What is a good use-case for hiding a non-virtual parent if it's so easy to get unexpected behavior?
If anyone is curious as to why I pose this question - I was recently examining Castle Projects DynamicProxy library. The one main hurdle in using it is that any method (or property) you want to proxy has to be virtual. And this isn't always an option for developers (if we don't have control over the source). Not to mention the purpose of DynamicProxy is to avoid-coupling between your proxied class and whatever behavior you are trying to achieve with the proxy (such as Logging, or perhaps a Memoization implementation). And by forcing virtual methods to accomplish this what is instead achieved is very thin but obtuse coupling of DynamicProxy to all the classes it is proxying - Imagine, you have a ton of methods labeled virtual even though they are never inherited and overridden, so any other developer looking at the code might wonder "why are these even virtual? lets change them back".
Anyway, the frustration there led me to wonder what the benefits are of non-virtual, when it seems having everything virtual might have been more clear (IMO, I suppose) and perhaps(?) have more benefits.
EDIT: Labeling as community wiki, since it seems like a question that might have subjective answers
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
因为您不希望人们重写您没有为其设计类的方法。 需要付出巨大的努力才能确保重写方法甚至从类派生的安全性。 如果您没有考虑可能发生的情况,那么将其设置为非虚拟的会更安全。
Because you don't want people overriding methods that you haven't designed the class for. It takes a significant effort to make sure it is safe to override a method or even derive from a class. It's much safer to make it non-
virtual
if you haven't considered what might happen.Eric Lippert 在此介绍了方法隐藏
Eric Lippert covers this here, on method hiding
在许多情况下,给定方法具有特定的行为对于类的正常运行至关重要。 如果在继承类中重写该方法,则无法保证该方法将正确实现预期行为。 如果您的类是专门为继承而设计的并且将支持具有不同实现的方法,则仅应将方法标记为虚拟。 设计继承并不容易,在很多情况下,错误地重写方法会破坏类的内部行为
In many cases, it is crucial for a class to function properly that a given method has a specific behavior. If the method is overridden in an inherited class, there is no guarantee that the method will correctly implement the expected behavior. You should only mark a method virtual if your class is specifically designed for inheritance and will support a method with a different implementation. Designing for inheritance is not easy, there are many cases where incorrectly overriding a method will break the class's internal behavior
简单:类的全部要点就是封装某种抽象。 例如,我们想要一个充当文本字符串的对象。
现在,如果一切都是虚拟的,我就可以这样做:
然后将其传递给某个需要字符串的函数。 当他们试图修剪那根绳子时,它就爆炸了。
该字符串不再表现为字符串。 这怎么可能是一件好事呢?
如果一切都是虚拟的,那么你将很难强制执行类不变量。 您允许类抽象被破坏。
默认情况下,类应该封装它期望遵循的规则和行为。 原则上,您虚拟的所有内容都是可扩展性钩子,该函数可以更改为执行任何操作。 这仅在少数情况下有意义,即当我们的行为实际上是用户定义的时。
类有用的原因是它们允许我们忽略实现细节。 我们可以简单地说“这是一个字符串对象,我知道它将表现为字符串。我知道它永远不会违反任何这些保证”。 如果不能维持这种保证,那么该类就毫无用处。 您不妨将所有数据成员公开,并将成员方法移到类之外。
你知道里氏替换原则吗?
任何需要基类 B 的对象的地方,都应该能够传递派生类 D 的对象。这是面向对象编程的最基本规则之一。 我们需要知道,当我们将派生类向上转换为基类并将它们传递给需要基类的函数时,派生类仍然可以工作。 这意味着我们必须使某些行为固定且不可改变。
Simple: The entire point in a class is to encapsulate some kind of abstraction. For example, we want an object that behaves as a text string.
Now, if everything had been virtual, I would be able to do this:
and then pass this to some function that expects a string. And the moment they try to trim that string, it explodes.
The string no longer behaves as a string. How is that ever a good thing?
If everything is made virtual, you're going to have a hard time enforcing class invariants. You allow the class abstraction to be broken.
By default, a class should encapsulate the rules and behaviors that it is expected to follow. Everything you make virtual is in principle an extensibility hook, the function can be changed to do anything whatsoever. That only makes sense in a few cases, when we have behavior that is actually user-defined.
The reason classes are useful is that they allow us to ignore the implementation details. We can simply say "this is a string object, I know it is going to behave as a string. I know it will never violate any of these guarantees". If that guarantee can not be maintained, the class is useless. You might as well just make all data members public and move the member methods outside the class.
Do you know the Liskov Substitution Principle?
Anywhere an object of base class B is expected, you should be able to pass an object of derived class D. That is one of the most fundamental rules of object-oriented programming. We need to know that derived classes will still work when we upcast them to the base class and pass them to a function that expect the base class. That means we have to make some behavior fixed and unchangeable.
非虚拟方法的一个主要好处是它可以在编译时绑定。 也就是说,当代码中使用某个方法时,编译器可以确定实际要调用哪个方法。
如果该方法被声明为虚拟,则在编译时无法知道要调用的实际方法,因为该引用实际上可能指向已覆盖它的子类型。 因此,当需要解析实际调用的方法时,运行时会产生少量开销。
One key benefit of a non-virtual method is that it can be bound at compile time. That is the compiler can be sure which actual method is to be called when a method is used in code.
The actual method to be called cannot be known at compile time if that method is declared virtual, since the reference may actually point to a sub-type that has overriden it. Hence there is a small overhead at runtime when the actual method to call needs be resolved.
在框架中,可以调用非虚拟成员并具有一系列预期输出,如果该方法是虚拟的,则该方法的结果可能是未经测试的预期结果。 允许方法是非虚拟的可以为框架操作带来预期的结果。
In a framework, a non-virtual member could be called and have a range of expected outputs, if the method was virtual the result of the method could be an expected result that wasn't tested for. Allowing methods to be non-virtual give expected results to framework actions.