Click here to monitor SSC

Implementing method override covariance on C# (Part 3)

Published 19 July 2010 5:30 pm

To recap from my previous post, we’re trying to create a class that has the same behaviour as an explicit interface implementation, in which we can change the return type to a more specific type, but for class overrides instead of interface implementations.

(Ab)using explicit overrides

To implement explicit interface implementations on the CLR, the C# compiler uses the .override directive I discussed in my previous post. For example, in the following code:

public interface IA {
    object Method();
}

public class B : IA {
    public string Method() {
         /* ... */
    }
    
    object IA.Method() {
        return Method();
    }
}
the C# compiler turns the IA.Method explicit implementation into the following IL method declaration (don’t be put off by the dots in the method name; they are simply a rename to prevent name clashes within the containing class):
method private hidebysig newslot virtual final
  instance object IA.Method() cil managed {
    .override IA::Method
    
    ldarg.0
    call instance string B::Method()
    ret
}

Although not allowed in C#, IL allows us to do the same thing with class overrides. With a small example, we can see that this accomplishes what we want:

Calling A.Method on A: System.Object
Calling B.Method on B: System.String
Calling A.Method on B: System.String
When A.Method() is called from C#, the compile-time result is an object, and when B.Method() is called, the compile-time result is a string. Mission accomplished.

What about class hierarchies?

However, there is a problem with this. This works for the simple case of B overriding A, but what if we add in a third class C that inherits off B?

The IL code for this example approximates to the following C# code:

public class A {
    public object Method() {
        return new object();
    }
}

public class B : A {
    // String and Int32 both implement IComparable
    public IComparable Method() {
         return 1;
    }    
    // explicit override of A.Method()
    override object A.Method() {
        return Method();
    }
}

public class C : B {
    public string Method() {
        return String.Empty;
    }
    // further explicit override of A.Method
    override object A.Method() {
        return Method();
    }
}

Running this, we can see that this approach doesn’t work (B.Method returns an Int32 as an IComparable):

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

Here, the virtual dispatch isn’t working when calling B.Method on an instance of C. This is because the virtual dispatch only works when calling A.Method, as that is the only method that is overridden. Well, we can fix this by moving the actual method implementation into the explicit override, so that all the variance methods call A.Method and hence invoke the virtual dispatch mechanism, although this does lose us some verification safety by using an explicit cast. The IL code approximates to the following C# code:

public class A {
    public object Method() {
        return new object();
    }
}

public class B : A {
    // String and Int32 both implement IComparable
    public IComparable Method() {
        return (IComparable)((A)this).Method();
    }    
    override object A.Method() {
        return 1;
    }
}

public class C : B {
    public string Method() {
        return (string)((A)this).Method();
    }
    
    override object A.Method() {
        return String.Empty;
    }
}

And this works as expected:

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

If an assembly containing this code is referenced & used from C#, we will get the behaviour that we set out to achieve – calls to A.Method() return an object, B.Method() returns an IComparable, and C.Method() returns a string, with virtual dispatch calling the correct method according to the run-time type of the instance.

Applying this to existing assemblies

To apply this to existing assemblies as a proof-of-concept, I’ve written a small program using Mono Cecil to apply this transformation to any method in an assembly with an OverrideVarianceAttribute applied to the return type. To return to the factory example in my first post, it turns this:

class Foo1 {}
class Foo2 extends Foo1 {}

class Foo1Factory {
    public virtual Foo1 GetFoo() { /* ... */ }
}

class Foo2Factory extends Foo1Factory {
    [return: OverrideVariance(typeof(Foo2))]
    public override Foo1 GetFoo() { /* ... */ }
}
into this:
class Foo1Factory {
    public virtual Foo1 GetFoo() { /* ... */ }
}

class Foo2Factory extends Foo1Factory {
    public Foo2 GetFoo() {
        return (Foo2)((Foo1Factory)this).GetFoo();
    }
    override Foo1 Foo1Factory.GetFoo() { /* ... */ }
}

Although the CLR does not support true override variance, we’ve managed to create a transformation that produces the same behaviour, both for compile-type type checking and run-time virtual dispatch. Mission accomplished!

One Response to “Implementing method override covariance on C# (Part 3)”

  1. qwertie says:

    Very interesting! The C# compiler should really provide this technique as a stopgap measure until the CLR people figure out that return type covariance is a worthwhile feature (it blows my mind that they saw the value of generics variance, but not return covariance). It requires a redundant cast, but if the derived class is sealed then the compiler could use the faster technique that you presented originally.

Leave a Reply