Blog Stats
  • Posts - 44
  • Articles - 0
  • Comments - 67
  • Trackbacks - 0

 

Subterranean IL: Explicit overrides

Normally, virtual method overrides in .NET are done implicitly; if a subclass has a virtual method with the same name and signature as a virtual method in a base class, then the method in the subclass overrides the method in the base class:

.class public BaseClass {
    .method public instance virtual string Method1(int32 i1, object o1) { ... }
}

.class public SubClass extends BaseClass {
    // this method implicitly overrides BaseClass::Method1
    .method public instance virtual string Method1(int32 i1, object o1) { ... }
}

This is in contrast to C# and VB, where a 'base' virtual method has to be declared as virtual, and all overrides of that method in subclasses have to be declared as override. But, similarly to IL, exactly which method it overrides in a base type depends on the name and signature of the methods concerned.

Explicit interface implementations

So what happens with explicit interface implementations? Well, IL and the assembly metadata allow you to specify a MethodImpl, which forces a method in the class to override one with a different name (but the same signature) in a base class. C# uses this mechanism to implement explicit interface overrides; this C#:

public interface IInterface {
    string MyMethod(string s1);
}

public class MyClass : IInterface {
    public string MyMethod2(string s1) { ... }
    
    // explicit implementation of IInterface.MyMethod
    string IInterface.MyMethod(string s1) { ... }
}
is compiled to this IL:
.class public interface abstract IInterface {
    .method public abstract virtual instance string MyMethod(string s1) {}
}

.class public MyClass implements IInterface {
    // normal MyMethod2 method
    .method public instance string MyMethod2(string s1) {}

    // explicit implementation of IInterface.MyMethod
    .method private virtual final instance string MyApp.IInterface.MyMethod(string s1) {
        .override IInterface::MyMethod
        ...
    }
}

There are several interesting things to note here:

  1. The .override directive specifies the explicit override. Here, it forces MyClass::MyApp.IInterface.MyMethod to override IInterface::MyMethod.
  2. The explicit override method is named MyApp.IInterface.MyMethod. There is no necessity for any explicit overrides to be named anything in particular, but using this name format guarantees there won't be any name conflicts in the same class; neither with normal C# code, as that can't create method names with dots in, nor with any other explicit implementations, as the name contains the C# fully qualified name of the overridden method.
  3. The explicit override method is marked virtual final. In order to take part in any virtual method lookup, a method has to be declared virtual, even if the lookup information is 'forced' by an .override directive.
  4. The explicit override method is private. Although implicit overrides have to have at least the same accessibility as the method they're overriding (ie you can't implicitly override a public method with an internal one), these rules don't apply to explicit overrides. In this case, the explicit override can only be called within MyClass or through the IInterface interface.

Abusing explicit overrides

Although .override is used by the C# compiler for explicit interface implementation, it is not limited to that. In fact, the only specific rules governing the use of .override are:

  • The signatures of the methods have to match (as I explored in an earlier post, you can't implement override variance using explicit overrides). Explicit overrides only allow you to change the name.
  • The overridden method has to be in the type hierarchy of the containing class.
  • Both overridden and overriding methods have to be virtual.
  • The overridden method can't be final.

That's it. In particular, notice how there aren't any restrictions on whether the overridden method is on an interface or concrete base class:

.class public ToStringExplicitOverride extends [mscorlib]System.Object {

 .method private virtual instance string ExplicitToString() { .override [mscorlib]System.Object::ToString ldstr "Explicit override of object.ToString!" ret } }

or on the number of .override declarations on a particular method:

.class public ClassA {
    .method public virtual instance void Method1(string s, object o) { ... }
    .method public virtual instance void Method2(string s, object o) { ... }
}

.class public ClassB extends ClassA {
    .method public virtual instance void Method3(string s, object o) {
        .override ClassA::Method1
        .override ClassA::Method2
    }
}

or that the overriding method has to be private:

.class public ClassA {
    .method public virtual instance void Method1(string s, object o) { ... }
}

.class public ClassB extends ClassA {
    .method public virtual instance void Method2(string s, object o) {
        .override ClassA::Method1
    }
}

.class public ClassC extends ClassB {
    .method public virtual instance void Method2(string s, object o) {
        // this method will be invoked
// whenever ClassA::Method1 // is called on an instance of ClassC } }

or that the use of explicit and implicit overrides are exclusive of each other:

.class public ClassA {
    .method public virtual instance void Method1(string s, object o) { ... }
    .method public virtual instance void Method2(string s, object o) { ... }
}

.class public ClassB extends ClassA {
    .method public virtual instance void Method1(string s, object o) {
        .override ClassA::Method2
        
        // implements ClassA::Method1 implicitly,
// and ClassA::Method2 explicitly // this method will be invoked
// if ClassA::Method1 or ClassA::Method2 // is called on an instance of ClassB } .method public virtual instance void Method2(string s, object o) { // normally, this would
// implicitly override ClassA::Method2 // but this method will NOT be called
// if ClassA::Method2 is invoked // as Method1 explicitly overrides
// ClassA::Method2 instead } }

or any combination of these. The use of explicit overrides is limited only by the compiler writer's ingenuity.

Alternate explicit override syntax

As well as specifying .override inside the method body, you can also specify it in the class itself:

.class public EqualsClass extends [mscorlib]System.Object {
    .method public virtual instance bool AlternateEquals(object o) { ... }
    
    .override [mscorlib]System.Object::Equals with instance bool AlternateEquals(object)
}

This leads onto the question as to whether you can use this syntax to link two disparate arms of a type's inheritance heirarchy. As an example using implicit implementations:

.class public interface IInterface {
    .method public virtual abstract instance string Method1() {}
}

.class public abstract BaseClass {    
    .method public virtual instance string Method1() {
        ldstr "foo"
        ret
    }
}

.class public SubClass extends BaseClass implements IInterface {
    // SubClass implements IInterface::Method1
// using BaseClass::Method1 }
but does this work for explicit implementations?
.class public interface IInterface {
    .method public virtual abstract instance string Method1() {}
}

.class public abstract BaseClass {    
    .method public virtual instance string Method2() {
        ldstr "foo"
        ret
    }
}

.class public SubClass extends BaseClass implements IInterface {
    .override IInterface::Method1 with instance string BaseClass::Method2()
}
well, ilasm compiles it successfully, but it fails verification, and the CLR fails to load the dll:
[MD]: Error: Class implements interface but not method
    (class:0x02000004; interface:0x06000001; method:0x0069004d).
    [token:0x09000001]
[MD]: Error: MethodImpl has body from another TypeDef (token=0x02000003).
    [token:0x00000001]

Although this can be represented in the assembly metadata, the CLR specification disallows explicit overrides using methods in a base class (section 10.3.2). The overriding method must be in the same type as the .override directive.

This is a shame; this seems like a rather arbitary limitation, given that implicit implementations using base class methods work without errors. It would be interesting to hear from the CLR team as to why this limitation exists in the specification.

In the meantime, you can create a stub method in the implementing class to call the correct method on a base class (which is a technique sometimes used by the C# compiler).

Cross posted from Simple Talk.

Feedback

No comments posted yet.


Post A Comment
Title:
Name:
Email:
Comment:
Verification:
 
 

 

 

Copyright © simonc