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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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):
1 2 3 4 5 6 7 8 |
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:
1 2 3 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
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
):
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
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:
1 2 3 4 5 6 |
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:
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 8 9 10 |
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!
Load comments