Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
280 views
in Technique[技术] by (71.8m points)

c# - Covariance and Contravariance with Func in generics

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

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

The .net Func definition is as follows:

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

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

EDIT

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> ();
}

Given that public class AnotherClass : ISomeInterface.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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<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 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.

Now we can do this:

Func<Tiger, Animal> 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<A, B> f);:

  • 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.

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

instead.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...