2017-02-28 66 views
9

据我所知制作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? 
} 
+1

不,方法调用仍然是虚拟的。 – InBetween

+0

你几乎是对的 - 你需要将该方法标记为“密封”,而不是班级。 – Nat

+0

@Nat,但正如我下面评论 - 本文说,类可以标记密封http://codebetter.com/patricksmacchia/2008/01/05/rambling-on-the-sealed-keyword/我更容易标记类而不是每个虚拟方法。 –

回答

2

“我想我可以让这个私人的密封类”

在继承层次结构无法更改访问修饰符。这意味着,如果方法是在基类public你不能让它privateinternalprotected在派生类中。

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,那么您不能继承它,所以您不能继承它的方法。

关于虚拟表 - 如果方法重载,你从你不能在继承层次使用它使虚拟表中删除它,没有理由重写方法并没有在继承层次中使用它。

+0

性能怎么样?我需要这些方法不要查看VTable。我知道这个数量非常小,在大多数情况下可能并不重要,应该是制定方法密封的设计原因。那么,我必须标记每一个密封的方法,以便停止查看VTable? –

+1

如果您在'sealed'类中将方法标记为'sealed',则会得到相同的IL。见: 没有'密封' - http://prnt.sc/eecpnk,'密封' - http://prntscr.com/eecq2w –

+0

谢谢,那么我想这取决于如果类是密封的,那么有根据这篇文章,这个方法将被称为非虚拟方法:http://codebetter.com/patricksmacchia/2008/01/05/rambling-on-the-sealed-keyword/ –

0

密封意味着你不能从它继承或覆盖。如果你不能从类B继承,你无法覆盖方法M. 至于查找,我不认为你保存它。在您的示例调用方法中,O必须从类A中查找方法并调用它。这通过vtable进行。

0

制作密封不会有任何效果类或方法,以任何B.M()呼叫将是虚的。

如果我必须打赌为什么这样,我会说它是因为M()A中声明,覆盖它不会使该方法“属于”B

如果您检查生成的代码的IL,您将看到相关指令是callvirt instance string namespace.A::M(),因此,即使B被封闭,该呼叫也必须是虚拟的。

2

首先要注意的是,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你已经在一个你确信自己不会在另一个地方做某事的地方。暂停重新考虑是件好事。

如果我让一个类密封,这是否意味着类层次结构中的所有虚拟方法都被标记为密封?

它们被认为是密封的,就像明确标记的一样。