2

我从一个简单的通用接口开始:

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# 编译器不能用我原来的单一协变泛型接口在内部为我做这个技巧?

4

1 回答 1

0

因为TObjectused by方法是继承自接口的。所以当泛型接口类型被构造时,TObject就变成了方法的固定类型。

例如,如果接口构造为IFoo<Chrome>,则DoSomething期望Expression<Func<Chrome, TValue>>被传入。

将接口转换为IFoo<Browser>不会改变上面构造的方法。并且不允许将实例传递 Browser给期望的方法,这就是编译器抛出错误的原因。Chrome

但是,如果将定义 TObject移至方法,则在调用该方法之前不会构造它。即,如果您传入Browser的方法是使用Browser. 所以你没有编译器会发现的明显问题。如果您的方法实现期望Chrome通过其他浏览器,您将获得运行时异常。

尽管语法看起来相同,但编译后的代码并不在幕后。

于 2020-03-14T12:13:56.157 回答