Search code examples
c#overridingclrcil

Is overriding a final (IL) / sealed (C#) method with a different name legal?


I have a hierarchy of classes:

class C1 { virtual object M1(); }

class C2: C1 { override sealed object M1(); }

class C3: C2 { 
   // I want to override M1()
   // CSC gives me an error, obviously
   override object M1();
}

But it seems there is a way. In IL, you can override a method with a different name. So, we change the name (M1_2() overrides M1()), say it overrides the method on the base class (C1::M1()), a la explicit interface implementation, and the "final" on the intermediate (C2) class does not matter anymore.

.class public auto ansi beforefieldinit N.C3
 extends N.C2
{ 
   .method private hidebysig virtual final 
      instance object  M1_2() cil managed
   {
      .override N.C1::M1

ILasm will happily assemble it, and it shows in ILSpy as

public class C3 : C2
{
    object C1.M1_2()

Then in the same class, you can define a new M1 which calls this.M1_2(). So you have 1) overridden M1 (with a different name, but still...) and 2) have a M1 method in C3 (it is a "bridge", but it's what you see).

But it looks... wrong. Or is it something legal?

If you call

C1 obj = new C3();
obj.M1();

then M1_2 is called correctly (I verified it in the debugger). It seems that the CLR enforces the final constraint only if the chain is direct (C1::M1 > C2::M1 > C3::M1) and not if you do "jump" over the hierarchy (C1::M1 > C3::M1_2). You have to choose a different name, though. If you use the same name (M1):

.class public auto ansi beforefieldinit N.C3
   extends N.C2
{ 
   .method private hidebysig virtual final 
      instance object  M1() cil managed
   {
      .override N.C1::M1

will not work, throwing a System.TypeLoadException

Additional information: Declaration referenced in a method implementation cannot be a final method Which is totally expected.

I wonder: are those CLR rules, or I just found a corner case in the implementation? (A corner case in the rules would be fine, in the implementation.. you cannot count on it ;) )


Solution

  • Looks like an edge case in the specification.

    In ECMA-335, Partition II section 22.27 MethodImpl:

    1. MethodDeclaration shall index a method in the ancestor chain of Class (reached via its Extends chain) or in the interface tree of Class (reached via its InterfaceImpl entries) [ERROR]

    2. The method indexed by MethodDeclaration shall not be final (its Flags.Final shall be 0) [ERROR]

    So the specific method you try to override must not be sealed and must be defined on an ancestor class, but there is no requirement that the specific method specified is the most specific override for that slot in your ancestor chain.

    Having said that, this is probably "sufficiently unexpected" that future versions may impose security restrictions to do this sort of thing.