泛型中Func的协方差和协方差 [英] Covariance and Contravariance with Func in generics

查看:95
本文介绍了泛型中Func的协方差和协方差的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要有关泛型和委托中方差的更多信息.以下代码段无法编译:

I need more information about variance in generics and delegates. The following code snippet does not compile:

错误CS1961无效的方差:类型参数"TIn"必须为 在'Test.F(Func)'上协变量有效. "Tin"是 变质的.

Error CS1961 Invalid variance: The type parameter 'TIn' must be covariantly valid on 'Test.F(Func)'. 'TIn' is contravariant.

public interface Test<in TIn, out TOut>
{
    TOut F (Func<TIn, TOut> transform);
}

.net Func的定义如下:

The .net Func definition is as follows:

public delegate TResult Func<in T, out TResult> (T arg);

为什么编译器抱怨TIn是协变的,而TOut是协变的,而Func期望完全相同?

Why the compiler complains about TIn being contravariant and TOut - covariant while the Func expects exactly the same variance?

编辑

对我来说,主要的限制是我希望我的Test接口将TOut作为协变,以便使用它像这样:

The main constraint for me is that I want my Test interface to have TOut as covariant in order to use it something like this:

public Test<SomeClass, ISomeInterface> GetSomething ()
{
    return new TestClass<SomeClass, AnotherClass> ();
}

给出public class AnotherClass : ISomeInterface.

推荐答案

我需要有关泛型和委托中方差的更多信息.

I need more information about variance in generics and delegates.

我写了一系列有关此功能的博客文章.尽管其中有些已经过时了(因为它是在设计完成之前编写的),但是那里有很多很好的信息.特别是,如果您需要方差有效性的正式定义,则应仔细阅读以下内容:

I wrote an extensive series of blog articles on this feature. Though some of it is out of date -- since it was written before the design was finalized -- there's lots of good information there. In particular if you need a formal definition of what variance validity is, you should carefully read this:

https://blogs .msdn.microsoft.com/ericlippert/2009/12/03/exact-rules-for-variance-validity/

有关其他主题,请参见我在MSDN和WordPress博客上的其他文章.

See my other articles on my MSDN and WordPress blogs for related topics.

为什么编译器抱怨TIn是协变的,而TOut是协变的,而Func期望完全相同?

Why the compiler complains about TIn being contravariant and TOut - covariant while the Func expects exactly the same variance?

让我们稍微重写一下代码,看看:

Let's slightly rewrite your code and see:

public delegate R F<in T, out R> (T arg);
public interface I<in A, out B>{
  B M(F<A, B> f);
}

编译器必须证明这是安全,但事实并非如此.

The compiler must prove that this is safe, but it is not.

我们可以通过假设它是安全的,然后发现如何滥用它来说明它是不安全的.

We can illustrate that it is not safe by supposing that it is, and then discovering how it can be abused.

让我们假设动物的层次结构具有明显的关系,例如,哺乳动物是动物,长颈鹿是哺乳动物,依此类推.并假设您的差异注释是合法的.我们应该能够说:

Let's suppose we have an Animal hierarchy with the obvious relationships, eg, Mammal is an Animal, Giraffe is a Mammal, and so on. And let's suppose that your variance annotations are legal. We should be able to say:

class C : I<Mammal, Mammal>
{
  public Mammal M(F<Mammal, Mammal> f) {
    return f(new Giraffe());
  }
}

我希望您同意这是一个完全有效的实现.现在我们可以这样做:

I hope you agree this is a perfectly valid implementation. Now we can do this:

I<Tiger, Animal> i = new C();

C实现了I<Mammal, Mammal>,我们已经说过第一个可以更具体,而第二个可以更一般,所以我们做到了.

C implements I<Mammal, Mammal>, and we've said that the first one can get more specific, and the second can get more general, so we've done that.

现在我们可以这样做:

Func<Tiger, Animal> f = (Tiger t) => new Lizard();

对于此委托人来说,这是一个完全合法的lambda,并且与以下签名匹配:

That's a perfectly legal lambda for this delegate, and it matches the signature of:

i.M(f);

会发生什么? C.M期望可以带长颈鹿并返回哺乳动物的功能,但是已经赋予它带老虎并可以返回蜥蜴的功能,因此某人将会度过非常糟糕的一天.

And what happens? C.M is expecting a function that takes a giraffe and returns a mammal, but it's been given a function that takes a tiger and returns a lizard, so someone is going to have a very bad day.

这绝对不能允许发生,但是此过程中的每一步都是合法的.我们必须得出的结论是,方差本身并不是可证明的安全性,实际上,事实并非如此.编译器拒绝该错误是正确的.

Plainly this must not be allowed to happen, but every step along the way was legal. We must conclude that the variance itself was not provably safe, and indeed, it was not. The compiler is right to reject this.

正确获取方差不仅需要简单地匹配输入和输出注释. 您必须以不允许这种缺陷存在的方式进行操作.

Getting variance right takes more than simply matching the in and out annotations. You've got to do so in a manner that does not allow this sort of defect to exist.

这解释了为什么这是非法的.为了解释如何是非法的,编译器必须检查B M(F<A, B> f);是否满足以下条件:

That explains why this is illegal. To explain how it is illegal, the compiler must check that the following is true of B M(F<A, B> f);:

  • B协变有效的.由于它被声明为出",所以是.
  • F<A, B>相反有效的.它不是.泛型委托的有效反变量有效"定义的相关部分为:如果第ith个类型参数被声明为反变量,则Ti必须协变有效.确定.第一个 type参数 T被声明为反变量.因此,第一个 type参数 A必须是协变有效的.但是它不是协变量有效,因为它被宣布为协变量.那就是你得到的错误.同样,B也是不好的,因为它必须是反变量有效的,而B是协变的.在找到此处的第一个问题之后,编译器不会继续查找其他错误.我考虑过,但由于它是太复杂的错误消息而拒绝了.
  • B is valid covariantly. Since it is declared "out", it is.
  • F<A, B> is valid contravariantly. It is not. The relevant portion of the definition of "valid contravariantly" for a generic delegate is: If the ith type parameter was declared as contravariant, then Ti must be valid covariantly. OK. The first type parameter, T, was declared as contravariant. Therefore the first type argument A must be valid covariantly. But it is not valid covariantly, because it was declared contravariant. And that's the error you're getting. Similarly, B is also bad because it must be valid contravariantly, but B is covariant. The compiler does not go on to find additional errors after it finds the first problem here; I considered it but rejected it as being a too-complex error message.

我还注意到,即使委托不是变体,您也会仍然遇到此问题;在我的反例中,我们没有使用F在其类型参数中是变量的事实.如果我们尝试,将会报告类似的错误

I note also that you would still have this problem even if the delegate were not variant; nowhere in my counterexample did we use the fact that F is variant in its type parameters. A similar error would be reported if we tried

public delegate R F<T, R> (T arg);

相反.

这篇关于泛型中Func的协方差和协方差的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆