Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?
}
like image 778
Candid Moon _Max_ Avatar asked Feb 28 '17 10:02

Candid Moon _Max_


2 Answers

"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.

like image 138
Roman Doskoch Avatar answered Sep 22 '22 07:09

Roman Doskoch


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.

like image 21
Jon Hanna Avatar answered Sep 19 '22 07:09

Jon Hanna