如何摆脱虚拟表?密封类
据我所知制作sealed
去掉在VTable中查找还是我错?如果我创建了一个类sealed
,这是否意味着类层次结构中的所有虚拟方法也都被标记为sealed
?如何摆脱虚拟表?密封类
例如:
public class A {
protected virtual void M() { ........ }
protected virtual void O() { ........ }
}
public sealed class B : A {
// I guess I can make this private for sealed class
private override void M() { ........ }
// Is this method automatically sealed? In the meaning that it doesn't have to look in VTable and can be called directly?
// Also what about O() can it be called directly too, without VTable?
}
“我想我可以让这个私人的密封类”
在继承层次结构无法更改访问修饰符。这意味着,如果方法是在基类public
你不能让它private
或internal
或protected
在派生类中。
private new void M() { ........ }
我所知使密封摆脱在虚函数表查找或 我错了班就:只有当你宣布你的方法
new
您可以更改修改?
密封类在层次结构中位于最后,因为您无法继承它。虚拟表可以与sealed
类一起使用,以防sealed
类重写基类中的某些方法。
如果我把一个类封了,这是否意味着所有虚拟方法在 类层次结构中也被标记为密封?
你可以看到IL代码:
.method family hidebysig virtual // method is not marked as sealed
instance void M() cil managed
{
.maxstack 8
IL_0000: nop
IL_0001: ret value
}
方法不标记为sealed
。即使您明确将此方法标记为sealed
,您也会得到相同的IL代码。
同样,没有理由在sealed
类来标记方法sealed
。如果class为sealed
,那么您不能继承它,所以您不能继承它的方法。
关于虚拟表 - 如果方法重载,你从你不能在继承层次使用它使虚拟表中删除它,没有理由重写方法并没有在继承层次中使用它。
性能怎么样?我需要这些方法不要查看VTable。我知道这个数量非常小,在大多数情况下可能并不重要,应该是制定方法密封的设计原因。那么,我必须标记每一个密封的方法,以便停止查看VTable? –
如果您在'sealed'类中将方法标记为'sealed',则会得到相同的IL。见: 没有'密封' - http://prnt.sc/eecpnk,'密封' - http://prntscr.com/eecq2w –
谢谢,那么我想这取决于如果类是密封的,那么有根据这篇文章,这个方法将被称为非虚拟方法:http://codebetter.com/patricksmacchia/2008/01/05/rambling-on-the-sealed-keyword/ –
密封意味着你不能从它继承或覆盖。如果你不能从类B继承,你无法覆盖方法M. 至于查找,我不认为你保存它。在您的示例调用方法中,O必须从类A中查找方法并调用它。这通过vtable进行。
制作密封不会有任何效果类或方法,以任何B.M()
呼叫将是虚的。
如果我必须打赌为什么这样,我会说它是因为M()
在A
中声明,覆盖它不会使该方法“属于”B
。
如果您检查生成的代码的IL
,您将看到相关指令是callvirt instance string namespace.A::M()
,因此,即使B
被封闭,该呼叫也必须是虚拟的。
首先要注意的是,C#通常会对引用类型上的任何实例方法进行虚拟调用,即使该方法不是虚拟的。这是因为有一个C#规则,在一个不是.NET规则的空引用上调用一个方法是非法的(在原始CIL中,如果您在空引用上调用方法,并且该方法本身不访问某个字段或虚拟方法,它工作正常),并使用callvirt
是一个便宜的方式来执行该规则。
对于非虚拟实例调用,在某些情况下,C#将生成call
而不是callvirt
,但在某些情况下显然引用不为空。特别是obj?.SomeMethod()
,因为?.
意味着已经发生空检查,所以如果SomeMethod()
不是虚拟的,则这将被编译为call
。这只发生在?.
之后,因为编译?.
的代码可以执行该检查,而它不会发生在if (obj != null){obj.SomeMethod();}
上,因为编译.
的代码不知道它在执行空检查。 涉及的逻辑是非常本地化的。
可能在CIL级别跳过虚拟表的虚拟表查找。这是base
调用的工作方式;将其编译为call
,而不是callvirt
。通过在构造obj?.SomeMethod()
中延伸,其中SomeMethod
是虚拟的并且被密封(无论是单独的还是因为obj
的类型被密封),那么理论上可以将call
编译成最派生类型的实现。虽然有一些额外的检查,特别是要确保它仍然正常工作,如果声明类型和密封类型之间的层次结构中的类添加或删除覆盖。这需要对层次结构有一定的全局知识(并保证知识不会改变,这意味着目前正在编译的程序集中的所有类型)才能保证优化的安全性。收益微乎其微。而且大部分时间仍然不可用,原因与大多数情况下甚至在非虚拟呼叫中使用callvirt
相同。
我不认为有什么地方sealed
会影响编译器如何生成调用,并且大多数情况下肯定不会影响它。
虽然抖动可以免费应用更多的知识,但是如果有的话会变得非常小。我肯定会推荐标记你认为不会被覆盖的类,如sealed
,如果抖动使用那个,那很好,但我推荐它的主要原因不是性能,而是正确性。如果你尝试覆盖一个你已经标记为sealed
的类,那么要么A.你刚刚改变了设计,并知道你必须删除sealed
(0.5秒的工作来删除它)或B你已经在一个你确信自己不会在另一个地方做某事的地方。暂停重新考虑是件好事。
如果我让一个类密封,这是否意味着类层次结构中的所有虚拟方法都被标记为密封?
它们被认为是密封的,就像明确标记的一样。
不,方法调用仍然是虚拟的。 – InBetween
你几乎是对的 - 你需要将该方法标记为“密封”,而不是班级。 – Nat
@Nat,但正如我下面评论 - 本文说,类可以标记密封http://codebetter.com/patricksmacchia/2008/01/05/rambling-on-the-sealed-keyword/我更容易标记类而不是每个虚拟方法。 –