Я начал с простого универсального c интерфейса:
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 );
Однако мне нужно было сделать обобщенный тип c этого интерфейса ковариантным (по причинам, по которым я не go включаю), однако это вызывает ошибку компилятора, потому что Func<T0,TReturn>
требует, чтобы T0
был контравариантным (in T0
) или инвариантным параметром:
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 );
Таким образом, я получаю эту ошибку компилятора для объявления DoSomething
:
Ошибка CS1961 Недопустимая дисперсия: параметр типа 'TObject' должен быть неизменно допустимым для 'IFooContext<TObject>.DoSomething<TValue>(Expression<Func<TObject, TValue>>)
'. «TObject» является ковариантным.
После бросания различных идей в стену я обнаружил, что могу обойти это, переместив DoSomething
в не универсальный c интерфейс и указав его параметр TObject
, указанный в метод, а затем «выставить» первоначально намеченный метод как метод расширения следующим образом:
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 );
И он компилируется и запускается без проблем - и синтаксис фактического использования идентичен синтаксису предполагаемого использования моего более раннего примера.
Почему это работает и почему компилятор C# не может выполнить этот трюк для меня внутренне с моим оригинальным единственным ковариантным интерфейсом generi c?