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?