繁体   English   中英

如何摆脱虚拟表? 密封类

[英]How to get rid of virtual table? Sealed class

据我所知,制作类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 ,则不能将其设置为privateinternal或在派生类中protected 只有在将方法声明为new方法时,才能更改修改器:

private new void M() { ........ }

据我所知,制作类密封摆脱了VTable中的查找或我错了吗?

密封类是层次结构中的最后一个,因为您无法继承它。 虚拟表可以与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代码。

此外,没有任何理由因为标记方法sealedsealed类。 如果类是sealed你不能继承它,所以,你不能继承它的方法。

关于虚拟表 - 如果方法覆盖并且您从虚拟表中删除它,则永远不能在继承层次结构中使用它,因此,没有理由覆盖方法并且永远不会在继承层次结构中使用它。

首先要注意的是,C#通常会对引用类型上的任何实例方法进行虚拟调用,即使该方法不是虚拟的。 这是因为有一个C#规则,在不是.NET规则的空引用上调用方法是非法的(如果在空引用上调用方法并且该方法本身不访问字段,则在原始CIL中或虚拟方法,它工作正常)和使用callvirt是一种廉价的方式来执行该规则。

在某些情况下,C#将为非虚拟实例调用生成call而不是callvirt ,在这种情况下,引用显然不为null。 特别是与obj?.SomeMethod()以来的?. 表示已经发生了空检查,如果SomeMethod()不是虚拟的,则会编译为call 这只发生在?. 既然编译的代码?. 可以做那个检查,而if (obj != null){obj.SomeMethod();}不会发生,因为代码编译. 不知道它是否在进行空检查。 涉及的逻辑是非常本地化的

在CIL级别可以跳过虚拟表查找虚拟方法。 这是base调用的工作方式; 编译到一个call在该基地的实施方法,而不是一个的callvirt 通过扩展构造obj?.SomeMethod() ,其中SomeMethod是虚拟的并且是密封的(无论是单独的还是因为obj的类型是密封的),理论上可以将其编译为对最派生类型的实现的call 虽然声明类型和密封类型之间的层次结构中的类添加或删除覆盖,但仍需要进行一些额外的检查以确保它仍能正常工作。 它需要一些全局的层次结构知识(并保证知识不会改变,这意味着当前正在编译的程序集中的所有类型)才能使优化变得安全。 收益很小。 并且在大多数情况下仍然不可用,因为大多数时候即使在非虚拟呼叫上使用callvirt也是如此。

我认为sealed不会影响编译器生成调用的方式,而且大多数情况下肯定不会影响它。

抖动可以自由地应用更多的知识,但是如果有的话,差异将会非常小。 我当然建议将你知道不会被覆盖的类标记为sealed ,如果抖动使用了那么那就太好了,但我推荐它的主要原因不是性能而是正确性。 如果你某个地方试图覆盖你标记为sealed的类,那么要么A.你刚刚改变了设计并且知道你必须删除sealed (.5秒工作以删除它)或B.你已经在一个地方做了一件事,你确定你不会在另一个地方。 暂时重新考虑是件好事。

如果我使类密封,这是否意味着类层次结构中的所有虚方法也标记为密封?

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

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

密封类或方法将无效,对BM()任何调用都将是虚拟的。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM