我从一个简单的通用界面开始:
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 );
但是我需要使该接口的泛型类型成为协变量(出于我不愿讨论的原因),但这会导致编译器错误,因为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
移至非通用接口并在方法上指定其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#编译器无法在我的原始单一协变通用接口内部为我完成此技巧?
因为方法使用的TObject
是从接口继承的。因此,当构造通用接口类型时,TObject
成为该方法的固定类型。
例如,如果接口构造为IFoo<Chrome>
,则DoSomething
将期望传入Expression<Func<Chrome, TValue>>
。
将接口转换为IFoo<Browser>
不会更改上面构造的方法。并且不允许将Browser
的实例传递给期望Chrome
的方法,因此这就是编译器引发错误的原因。
但是,如果将TObject
的定义移到方法中,则直到调用该方法之前,都不会构造它。因此,编译器不会发现任何明显的问题。如果您的方法实现期望在其他浏览器上使用Chrome
,则将获得运行时异常。
尽管语法看起来相同,但并不复杂。