9

I'm learning C# generics and making some dummy code for testing purposes. So, I'm testing the in Generic Modifier, which specifies that the type parameter is contravariant.

Given the below interface:

public interface IInterfaceTest<in T>
{
    void Method(T value);
    void Method(IList<T> values);
    void Method(IEnumerable<T> values);
}

When compiling, I'm getting the error message:

[CS1961] Invalid variance: The type parameter 'T' must be invariantly valid on 'IInterfaceTest.Method(IList)'. 'T' is contravariant.

The error is related only with the line void Method(IEnumerable<T> values). If this line is removed, all works fine.

So my question is: Why can I use the generic contravariant with IEnumerable but does not with IList? Am I forgot something?

Thanks.

Bruno Peres
  • 15,845
  • 5
  • 53
  • 89
  • 5
    `IList` is not, and cannot be, covariant. – SLaks Sep 28 '17 at 16:44
  • 2
    @SLaks: I think OP gets that, but why? (which will help me as well) – Stefan Sep 28 '17 at 16:45
  • 6
    Because it's mutable. https://stackoverflow.com/a/2033921/34397 – SLaks Sep 28 '17 at 16:46
  • 3
    tl;dr: `IEnumerable` won't return anything less specialized than T, so that's OK. `List.Add(SubclassOfT)` *requires* an argument more specialized than `T`. – 15ee8f99-57ff-4f92-890c-b56153 Sep 28 '17 at 16:52
  • Ed nailed it. The simplified rule is: covariant type parameters can only be used to take something _out_, and contravariant can only be used to put something _in_. Hence the declarations `out` and `in`. – Mike Strobel Sep 28 '17 at 17:03
  • Because, for a type parameter `T` with variance, the variance is effectively reversed for input types' relationships with `T` (but the same for output types' relationships). – Mike Strobel Sep 28 '17 at 17:21

1 Answers1

4

The question why it's not allowed for IList<T> has been answered in the comments and linked questions already: IList<T> is invariant in T and so a contra-variant T cannot be used here whatsoever.

What puzzled me at first is the fact that Method(IEnumerable<T>) is allowed here. The strange thing is that variance is "turned around" when you use the T as a type argument for another generic type.

Imagine this.

public interface ITestInterface<in T>
{
    void Method(IEnumerable<T> e);
    IEnumerable<T> GetMethod(); // illegal
}
public class Animal {}
public class Lion : Animal [}
public class Gnu : Animal {}

ITestInterface<Animal> animals;
ITestInterface<Lion> lions;
ITestInterface<Gnu> gnus;

Now the contra-variance of ITestInterface<in T> in T tells us that you can do

lions = animals;

And when you call lions.Method(e), you can only provide an IEnumerable<Lion>. So the code of Method can only enumerate Lions, which are all Animals as animals.Method() expects. Everything is fine.

On the other hand, the IEnumerable<T> GetMethod() is illegal, because:

gnus = animals;

is legal, and now gnu.GetMethod() would return an IEnumerable<Animal> where you'd expect an IEnumerable<Gnu>. And when you iterated, suprising animals could wait in that sequence.

René Vogt
  • 43,056
  • 14
  • 77
  • 99
  • 3
    The variance with respect to `T` is "turned around" for _inputs_ that have a relationship with `T`. Outputs must have the same variance with respect to `T`. For example, `Action GetMethod()` would be valid because `Action<-T>` varies with `T` in the same way as `ITestInterface<-T>`. Other than that, good answer. – Mike Strobel Sep 28 '17 at 17:24