C# 泛型中Func的协方差和逆变换

C# 泛型中Func的协方差和逆变换,c#,generics,variance,C#,Generics,Variance,我需要更多关于泛型和委托差异的信息。以下代码段未编译: 错误CS1961无效差异:类型参数“TIn”必须为 在“Test.F(Func)”上协变有效锡是 逆变的 为什么编译器抱怨TIn是逆变的和TOut-协变的,而Func却期望完全相同的方差 编辑 对我来说,主要的限制是我希望我的测试接口将TOut作为协变的,以便使用它,如下所示: public Test<SomeClass, ISomeInterface> GetSomething () { return new Test

我需要更多关于泛型和委托差异的信息。以下代码段未编译:

错误CS1961无效差异:类型参数“TIn”必须为 在“Test.F(Func)”上协变有效锡是 逆变的

为什么编译器抱怨
TIn
是逆变的和
TOut
-协变的,而Func却期望完全相同的方差

编辑

对我来说,主要的限制是我希望我的测试接口将TOut作为协变的,以便使用它,如下所示:

public Test<SomeClass, ISomeInterface> GetSomething ()
{
    return new TestClass<SomeClass, AnotherClass> ();
}
公共测试GetSomething()
{
返回新的TestClass();
}
假定
公共类另一个类:ISomeInterface

从接口定义中删除in和out-关键字:

public interface Test<TIn, TOut>{
    TOut F (Func<TIn, TOut> transform);
}
公共接口测试{
TOut F(Func变换);
}

删除输入和输出关键字:

public interface Test<TIn, TOut>
{
    TOut F (Func<TIn, TOut> transform);
}
公共接口测试
{
TOut F(Func变换);
}
您可以在此处了解它们的含义:

如果类型仅用作方法参数的类型,而不用作方法返回类型,则可以在泛型接口或委托中声明为逆变类型


type参数仅用作接口方法的返回类型,而不用作方法参数的类型。

Variance是指能够用比最初声明的派生类型更多或更少的派生类型替换类型参数。例如,
IEnumerable
对于
T
是协变的,这意味着如果您从对
IEnumerable
对象的引用开始,您可以将该引用分配给类型为
IEnumerable
的变量,其中
V
可从
U
分配(例如
U
继承
V
)。这是可行的,因为任何试图使用
IEnumerable
的代码都希望只接收
V
的值,并且由于
V
可从
U
赋值,因此只接收
U
的值也是有效的

对于协变参数,如
T
,您必须指定一个类型,其中目标类型与
T
相同,或可从
T
指定。对于逆变参数,它必须走另一条路。目标类型必须与类型参数相同或可分配给类型参数

那么,您试图编写的代码在这方面是如何工作的呢

当您声明
Test
时,您承诺将该接口的实例
Test
分配给任何具有类型
Test
的目的地是有效的,其中
U
可以分配给
TIn
,而
TOut
可以分配给
V
(当然,它们是相同的)

同时,让我们考虑一下您的<代码>转换< /COD>委托是什么。

Func
类型差异要求,如果要将该值分配给其他值,它也满足差异规则。也就是说,目标
Func
必须具有可从
T
分配的
U
,以及可从
V
分配的
TResult
。这可以确保期望接收值
U
的委托目标方法将获得其中一个值,并且接收该值的代码可以接受该方法返回的值(类型为
V

重要的是,您的接口方法
F()接口声明承诺
TOut
将仅用作接口成员的输出。但是通过使用
transform
委托,方法
F()
将接收一个值
TOut
,并将该值输入该方法。同样,允许方法
F()
TIn
的值传递给
transform
委托,使其成为接口实现的输出,即使您承诺
TIn
仅用作输入

换句话说,调用的每一层都颠倒了变化的意义。接口中的成员必须仅将协变类型参数用作输出,将逆变参数用作输入。但当在传递给接口成员或从接口成员返回的委托类型中使用这些参数时,它们在某种意义上会发生反转,并且必须遵守这方面的差异

一个具体的例子:

假设我们有一个接口的实现,
Test
。如果编译器允许您的声明,则允许您将该实现的值
Test
分配给类型为
Test
的变量。也就是说,最初的实现承诺允许任何类型为
对象
的对象作为输入,并且只返回类型为
字符串
的值。声明为
Test
的代码可以安全地使用它,因为它将
string
对象传递给需要
对象
值的实现(
string
对象
),它将从返回
string
值的实现中接收类型为
object
的值(同样,
string
对象,因此也是安全的)

但您的接口实现要求代码传递类型为
Func
的委托。如果允许您(如上所述)将接口实现视为
测试
,则使用重传实现的代码将能够将
Func
的委托传递给方法
F()
。实现中的方法
F()
允许将类型为
object
的任何值传递给委托,但该类型为
Func
的委托只希望传递类型为
字符串的值。如果
F()
传递了其他内容,例如仅传递了一个普通的旧
新对象()
public interface Test<TIn, TOut>{
    TOut F (Func<TIn, TOut> transform);
}
public interface Test<TIn, TOut>
{
    TOut F (Func<TIn, TOut> transform);
}
public delegate R F<in T, out R> (T arg);
public interface I<in A, out B>{
  B M(F<A, B> f);
}
class C : I<Mammal, Mammal>
{
  public Mammal M(F<Mammal, Mammal> f) {
    return f(new Giraffe());
  }
}
I<Tiger, Animal> i = new C();
Func<Tiger, Animal> f = (Tiger t) => new Lizard();
i.M(f);
public delegate R F<T, R> (T arg);