Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does "final" mean in IL?

When using ildasm/ilasm, you can observe the MSIL/CIL code produced by a compiler (the C# compiler, for example), and in some cases you can see that there are methods marked as virtual final.

What does final mean in this context? My guess is that it means "this method cannot be overridden", so even if this method has a slot in the virtual table, that slot cannot be overwritten by a method in a derived class.

But how? How does it work, in practice?

Is it just enforced by the compiler (e.g. if you try to override a sealed method, it will fail with a compilation error) or by the CLR (e.g. when you try the overriding method, the CLR throws), or by both? Is there any legal way of "removing", circumventing, this final, i.e. to write a derived class that overrides this method?

Update: I probably have found a way (see my own answer) but I am not sure at all this is legal, supported, standard... so I have posted another question on this new (but related) problem here

I am open to comments and, of course, alternative ways (if they exist!) to do it.

like image 387
Lorenzo Dematté Avatar asked Nov 26 '15 17:11

Lorenzo Dematté


2 Answers

This is for methods which are coming from some interface. It is an implementation detail. If a class implements some interface, then the method from interface is marked as virtual, that is for polymorphic behaviour (a place in virtual table (v table)). But it is also marked as final (Sealed) so that the further child classes implementing the base class can't override that particular method.

Consider the following example:

interface SomeInterface
{
    void SomeMethod();
}

class SomeClass : SomeInterface
{
    public void SomeMethod() //This will be marked as virtual final in IL
    {
        //anything
    }
}

From: CLR via C# (Jeffrey Richter)

The C# compiler requires that a method that implements an interface method signature be marked as public. The CLR requires that interface method be marked as virtual. If you do not explicitly mark the method as virtual in your source code, the compiler marks the method as virtual and sealed; this prevents a derived class from overriding the interface method. If you explicitly mark the method as virtual, the compiler marks the method as virtual (and leaves it unsealed); this allows a derived class to override the interface method.

like image 179
Habib Avatar answered Nov 01 '22 15:11

Habib


What does final mean in this context? My guess is that it means "this method cannot be overridden", so even if this method has a slot in the virtual table, that slot cannot be overwritten by a method in a derived class.

Yes. It is as such the equivalent of the C# keyword sealed so for example:

public override sealed string ToString()
{
  return "";
}

Becomes:

.method public final hidebysig virtual instance string ToString () cil managed 
{
  ldstr ""
  ret
}

virtual relates to the C# keyword virtual but also to any other virtual method. This includes override and also any interface implementation. In particular while you can't define a method as virtual sealed in C# (because it would be pointless, so you should decide just what you want to do) you can in CIL, and this is done in some interface implementations.

To the C# perspective there are three ways to implement an interface method or property:

  1. Non-virtual.
  2. Virtual (so derived classes can override it).
  3. Explicit implementation.

To the CIL perspective all of those are virtual as they all use the virtual-method mechanism. The first and third are also final.

(Consider that we can ignore the over-ride mechanism and use call in CIL to call a method as it is defined in a class, but we have to use callvirt with an interface because we don't know what class we are calling on, so there has to be a lookup).

Is it just enforced by the compiler (e.g. if you try to override a sealed method, it will fail with a compilation error) or by the CLR (e.g. when you try the overriding method, the CLR throws), or by both?

Both.

The C# compiler will not allow you to override a sealed method:

public class Test
{
  public override sealed string ToString()
  {
    return "a";
  }
}

public class Test1 : Test
{
  public override string ToString()
  {
    return "";
  }
}

This refuses to compile with compiler error CS0239: "'Test1.ToString()' : cannot override inherited member 'Test.ToString()' because it is sealed".

But if you force it by writing the CIL yourself:

.class public auto ansi beforefieldinit Test extends [mscorlib]System.Object
{
  .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
  {
    ldarg.0
    call instance void [mscorlib]System.Object::.ctor()
    ret
  }

  .method public final hidebysig virtual instance string ToString () cil managed 
  {
    ldstr "a"
    ret
  }
}

.class public auto ansi beforefieldinit Test2 extends Mnemosyne.Test
{
  .method public hidebysig specialname rtspecialname instance void .ctor () cil managed 
  {
    IL_0000: ldarg.0
    IL_0001: call instance void Mnemosyne.Test::.ctor()
    IL_0006: ret
  }

  .method public hidebysig virtual instance string ToString () cil managed 
  {
    ldstr ""
    ret
  }
}

Then if you call new Test().ToString() it returns "a" as you'd expect, but if you call new Test1().ToString() you get a runtime error:

System.TypeLoadException : Declaration referenced in a method implementation cannot be a final method.  Type: 'TestAssembly.Test2'.  Assembly: 'TestAssembly, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null'.

Indeed, this makes the entire class an unloadable type; it's new Test1() that throws, not the ToString().

Is there any legal way of "removing", circumventing, this final, i.e. to write a derived class that overrides this method?

No. The TypeLoadException will happen with any class that attempts to override a final method. Your only option is to edit the base class and compile it again.

like image 22
Jon Hanna Avatar answered Nov 01 '22 16:11

Jon Hanna