Covariance and Contravariance with Func in generics

前端 未结 5 2008
夕颜
夕颜 2021-01-02 06:51

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

Error CS1961 Invalid variance: The ty

5条回答
  •  有刺的猬
    2021-01-02 07:13

    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/

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


    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 (T arg);
    public interface I{
      B M(F 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
    {
      public Mammal M(F f) {
        return f(new Giraffe());
      }
    }
    

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

    I i = new C();
    

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

    Now we can do this:

    Func f = (Tiger t) => new Lizard();
    

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

    i.M(f);
    

    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.

    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 f);:

    • B is valid covariantly. Since it is declared "out", it is.
    • F 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.

    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 arg);
    

    instead.

提交回复
热议问题