19 July 2010

Implementing method override covariance on C# (Part 3)

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:

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):

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:

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:

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

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:

And this works as expected:

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:

into this:

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!

Keep up to date with Simple-Talk

For more articles like this delivered fortnightly, sign up to the Simple-Talk newsletter

This post has been viewed 4611 times – thanks for reading.

  • Rate
    [Total: 0    Average: 0/5]
  • Share