如何摆脱虚拟table?密封 class

How to get rid of virtual table? Sealed class

据我所知,使 class sealed 摆脱了在 VTable 中查找,还是我错了?如果我创建 class sealed 这是否意味着 class 层次结构中的所有虚拟方法也被标记为 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?
}

"I guess I can make this private for sealed class"

您不能更改继承层次结构中的访问修饰符。这意味着如果方法在基础 class 中是 public,则不能在派生的 class 中使它成为 privateinternalprotected。仅当将方法声明为 new:

时才能更改修饰符
private new void M() { ........ }

As far as I know making class sealed gets rid of look up in VTable or am I wrong?

Sealed class 在层次结构中排在最后,因为您不能从它继承。虚拟 table 可以与 sealed class 一起使用,以防此 sealed class 覆盖基础 class 中的某些方法。

If I make a class sealed does this mean that all virtual methods in class hierarchy are also marked 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 class 中将方法标记为sealed。如果 class 是 sealed 你不能继承它,你不能继承它的方法。

关于 virtual tables - 如果 method 被覆盖并且你从 virtual table 中删除它你永远不能在继承层次结构中使用它所以,没有理由覆盖方法并且永远不要使用它在继承层次结构中。

Sealed 意味着您无法继承或覆盖它。如果你不能从 class B 继承,你就没有办法覆盖方法 M。 至于查找,我认为您不会保存它。在您的示例中,调用方法 O 必须从 class A 中查找方法并调用它。这是通过 vtable 发生的。

使 class 或方法密封将无效,对 B.M() 的任何调用都将是虚拟的。

如果我不得不打赌为什么会这样,我会说是因为 M() 是在 A 中声明的,覆盖它不会使方法 "belong" 变为 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 级别跳过对虚拟方法的虚拟 table 查找。这就是 base 呼叫的工作方式;编译为 call 上的方法实现,而不是 callvirt。通过在构造 obj?.SomeMethod() 中扩展,其中 SomeMethod 是虚拟的和密封的(无论是单独的还是因为 obj 的类型被密封)然后理论上可以将其编译为 call 到最派生类型的实现。尽管在声明类型和密封类型之间的层次结构中添加或删除重写 classes 时,需要特别进行一些额外的检查以确保它仍然正常工作。它需要对层次结构有一些全局了解(并确保知识不会改变,这意味着当前正在编译的程序集中的所有类型)才能使优化安全。而且收益很小。而且大部分时间仍然不可用,原因与 callvirt 在大多数时间甚至在非虚拟呼叫中使用的原因相同。

我认为 sealed 不会在任何地方影响编译器生成调用的方式,而且大多数时候肯定不会影响它。

虽然抖动可以自由应用更多知识,但如果有的话,差异也会非常小。我当然建议将 classes 标记为 sealed,如果抖动利用了它,那很好,但我推荐它的主要原因不是性能而是正确性。如果你在某个地方试图覆盖你标记为 sealed 的 class 那么要么 A. 你只是稍微改变了设计并且知道你必须删除 sealed (. 5seconds work to remove it) o​​r B. 你在一个地方做了一些你确信你不会在另一个地方做的事情。重新考虑的短暂停顿很好。

If I make a class sealed does this mean that all virtual methods in class hierarchy are also marked sealed?

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