Click here to monitor SSC

Implementing method override covariance on C# (Part 2)

Published 16 July 2010 3:50 pm

So, C# doesn’t allow us to change the return type when overriding methods. Could we do it in IL?

For reference, here is a C# version of the initial example in my previous blog post, and the resulting IL class & method signatures generated by the C# compiler:

public class A {
public virtual object Method() { /* ... */ }
}

public class B : A {
public override object Method() { /* ... */ }
}


.class public auto ansi beforefieldinit A extends [mscorlib]System.Object {

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed { /* ... */ }

.method public hidebysig newslot virtual
instance object Method() cil managed
{ /* ... */ }
}

.class public auto ansi beforefieldinit B extends A {

.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed { /* ... */ }

// implicitly overrides A.Method
.method public hidebysig virtual
instance object Method() cil managed
{ /* ... */ }
}

Attempt 1: Implicit method overrides in IL

To help investigate this problem, I’ve created a small IL program that demonstrates the virtual method dispatch we need to subvert. Here is the output of the example without override covariance:

Calling A.Method on A: System.Object
Calling B.Method on B: System.String
Calling A.Method on B: System.String

To call a method in IL, you need to use the call instruction, which just calls the method directly, or callvirt, which calls the method with virtual dispatch. For example, to call System.Object.ToString() on an object currently on the stack, the instruction is:

callvirt instance string [mscorlib]System.Object::ToString()
Here we can immediately see a problem – the method token indicating which method to call includes the return type (this is why methods can be overridden by return type in IL). And, as we can see above, IL method overridding is done implicitly by signature, by missing out the newslot keyword in the method declaration.

It follows that if we change B.Method to return a string (example code), dynamic dispatch doesn’t work anymore, as it is now a completely different method to A.Method:

Calling A.Method on A: System.Object
Calling B.Method on B: System.String
Calling A.Method on B: System.Object

Attempt 2: Explicit method overrides in IL

OK, so implicit overriding won’t work. However, in IL there is an .override directive that can be applied to methods, which specifies explicit method overrides. Could we use this to force covariant method overrides by adding this to the B.Method declaration (example here)?

.method public virtual instance string Method() cil managed {
.override A::Method
/* ... */
ret
}

Unfortunately not – although it compiles ok, the resulting executable fails verification, and throws an exception when you try to run it:

System.MissingMethodException: Method not found: 'System.String A.Method()'
So it seems we can’t do exactly what we want using IL.

Taking a step back

Might we be able to create something that has the same behaviour as covariant method overrides without actually overriding directly? A workaround currently exists in C# for interfaces where you can create methods that behave the same as covariant interface methods using explicit implementations:

public interface IA {
object Method();
}

public class B : IA {
public string Method() {
/* ... */
}

object IA.Method() {
return Method();
}
}

// when using these classes:
IA a = new B();
B b = new B();

object ao = a.Method();
string bo = b.Method();

If we could do something similar with class overrides, we would be able to simulate covariant method overrides in the same way. In my next post, I’ll be looking at ways of doing exactly that.

Leave a Reply