I started off with a simple generic interface:
interface IFooContext<TObject>
{
TObject Value { get; }
String DoSomething<TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}
// Usage:
IFooContext<Panda> ctx = ...
String str = ctx.DoSomething( panda => panda.EatsShootsAndLeaves );
However I needed to make this interface's generic type covariant (for reasons I won't go into), however this causes a compiler error because Func<T0,TReturn> requires T0 to be contravariant (in T0) or
invariant parameter:
interface IFooContext<out TObject>
{
TObject Value { get; }
String DoSomething<TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}
// Intended usage:
IFooContext<Panda> ctx1 = ...
IFooContext<Ursidae> ctx2 = ctx1; // yay for covariance!
String str = ctx2.DoSomething( bear => bear.PoopsInTheWoods );
So I get this compiler error for the DoSomething declaration:
Error CS1961 Invalid variance: The type parameter 'TObject' must be invariantly valid on '
IFooContext<TObject>.DoSomething<TValue>(Expression<Func<TObject, TValue>>)'. 'TObject' is covariant.
After throwing various ideas at the wall I found out that I can work-around this by moving DoSomething to a non-generic interface and have its TObject parameter specified on the method, then "expose" the originally intended method as an extension method like so:
interface IFooContext
{
String DoSomething<TObject,TValue>( Expression<Func<TObject,TValue>> lambdaExpression );
}
interface IFooContext<TObject>
{
TObject Value { get; }
}
public static class FooContextExtensions
{
public static String DoSomething<TObject,TValue>( this IFooContext<TObject> context, Expression<Func<TObject,TValue>> lambdaExpression )
{
return context.DoSomething<TObject,Value>( lambdaExpression );
}
}
// Actual usage:
IFooContext<Panda> ctx1 = ...
IFooContext<Ursidae> ctx2 = ctx1; // yay for covariance!
String str = ctx2.DoSomething( bear => bear.PoopsInTheWoods );
And this compiles and runs without any problems - and the syntax of the actual usage is identical to that of the intended usage of my earlier sample.
Why does this work and why can't the C# compiler do this trick for me internally with my original single covariant generic interface?
Because the
TObjectused by method is inherited from the interface. So when the generic interface type is constructed,TObjectbecomes a fixed type to the method.For example, if the interface is constructed as
IFoo<Chrome>, thenDoSomethingwill expectExpression<Func<Chrome, TValue>>to be passed in.Converting the interface to
IFoo<Browser>will not change the constructed method above. And passing an instance ofBrowserto a method expectingChromeis not allowed, so that’s why the compiler throws an error.However, if you move definition of
TObjectto method, it will not be constructed until the method is called. i.e. if you passBrowserin the method is constructed usingBrowser. So you don’t have an obvious problem which the compiler would figure out. If your method implementation is expectingChromeover other browsers, you will get a runtime exception.Though the syntax looks the same, the compiled code is not under the hood.