简体   繁体   中英

How to get rid of virtual table? Sealed class

As far as I know making class sealed gets rid of look up in VTable or am I wrong? If I make a class sealed does this mean that all virtual methods in class hierarchy are also marked sealed ?

For example:

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"

You can't change access modifier in inheritance hierarchy. It means if method is public in base class you can't make it private or internal or protected in derived classes. You can change modifier only if you declare your method as 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 is last in hierarchy because you can't inherit from it. Virtual table can be used with sealed class in case this sealed class overrides some method from base class.

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

You can see IL code:

.method family hidebysig virtual            // method is not marked as sealed
    instance void M () cil managed 
{        
    .maxstack 8

    IL_0000: nop 
    IL_0001: ret value
}  

Method is not marked as sealed . Even if you explicitly mark this method as sealed you will get the same IL code.

Also, there is no reason to mark method as sealed in sealed class. If class is sealed you can't inherit it so, you can't inherit it's methods.

About virtual tables - if method is overriding and you delete it from virtual table you can never use it in inheritance hierarchy so, there is no reason to override method and never use it in inheritance hierarchy.

The first thing to note is that C# generally makes virtual calls to any instance method on a reference type, even when the method isn't virtual. This is because there is a C# rule that it's illegal to call a method on a null reference that isn't a .NET rule (in raw CIL if you call a method on a null reference and that method doesn't itself access a field or virtual method, it works fine) and using callvirt is a cheap way to enforce that rule.

C# will generate call rather than callvirt for non-virtual instance calls in some cases where it is obvious that the reference isn't null. In particular with obj?.SomeMethod() since the ?. means a null-check is already happening, then if SomeMethod() isn't virtual this will be compiled to call . This only happens with ?. since the code for compiling ?. can do that check whereas it doesn't happen with if (obj != null){obj.SomeMethod();} because the code compiling . doesn't know it is following a null-check. The logic involved is very localised .

It is possible at the CIL level to skip a virtual table lookup for virtual methods. This is how base calls work; compiled to a call on the base's implementation of a method rather than a callvirt . By extension in the construct obj?.SomeMethod() where SomeMethod was virtual and sealed (whether individually or because the type of obj was sealed) then it would be theoretically possible to compile that as a call to the most derived type's implementation. There are though some extra checks that need to be made in particular to make sure it still works correctly if classes in the hierarchy between the declaring type and the sealed type add or remove overrides. It would require some global knowledge of the hierarchy (and assurance that knowledge won't change, which means all the types being in the assembly currently being compiled) for the optimisation to be safe. And the gain is tiny. And still not available most of the time for the same reason that callvirt is used even on non-virtual calls most of the time.

I don't think there's anywhere where sealed affects how the compiler generates the call, and it certainly isn't affecting it most of the time.

The jitter is free to apply more knowledge though, but again the difference if any is going to very small. I'd certainly recommend marking classes you know won't be overridden as sealed and if the jitter makes use of that then that's great, but the main reason I'd recommend it isn't performance but rather correctness. If you somewhere try to override a class that you had marked sealed then either A. You've just changed the design a bit and knew you'd have to remove the sealed (.5seconds work to remove it) or B. You've done something in one place you were sure you wouldn't in another. It's good to have the brief pause of reconsidering.

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

They are considered sealed the same as if explicitly marked as such.

Sealed means that you cannot inherit from it or override it. If you cannot inherit from class B, you have no way of overriding method M. As for the lookup I don't think you save it. In your example calling method O would have to look up the method from class A and call it. That takes place through the vtable.

Making the class or method sealed will have no effect, any call to BM() will be virtual.

If I had to bet why this is so, I'd say its because M() is declared in A , overriding it doesn't make the method "belong" to B .

If you inspect the IL of the generated code, you will see that the relevant instruction is callvirt instance string namespace.A::M() , and therefore, even if B is sealed, the call must be virtual.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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