I've been hearing/reading a lot about covariance issues in C# and I wanted to pose a few questions & scenarios so hopefully I can clear up my confusion on the matter.
Throughout these examples, please assume the following is always defined:
public class Apple : Fruit {}
My first example:
IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;
This should work, correct? I tested this a couple of times in C# and it compiled fine & ran fine (my tests for my first example were slightly more than this, as I had polymorphic calls that printed stuff to the console).
Second example:
IList<Apple> apples = GetApples();
IList<object> fruits = apples;
In this second example, my understanding is that this should not compile and is the root of the covariance issues being resolved in .NET 4.0. Please correct me if I am wrong. I also am aware that .NET 4.0 does not allow covariance/contravariance between concrete types, only interfaces.
Finally, I'd like to get some definitions. I'm not quite clear of the meaning behind these 3 terms:
As for the last word, I used it a lot in C++ to refer to changes that have rules implied to them. For example, if I have an integer and it is only allowed to have a value between 1 and 10, the "invariance" is that it can only be between 1 and 10. I might be misunderstanding this and I'm also not sure if this definition translates well to C# for this specific discussion.
My goal is to understand exactly what the covariance or casting issues are with generic interfaces in C#. The examples I posted are my understanding of where the problem lies. If all examples compile/function fine, please present an example that does reproduce the most common covariance/contravariance/casting issues in C#. I need to know this so I can identify and explain the problem to others.
The IList<T>
interface is not defined to be covariant because it supports an Add
method which mutates the object.
Consider the following:
IList<Apple> apples = GetApples();
IList<object> fruits = apples;
fruits.Add(new Banana());
You could now get a Banana
from apples
, which is certainly not what was intended. Therefore, the IList
interface does not support covariance (and it never will), and should cause a compile error.
You should have the same problem with
IList<Apple> apples = GetApples();
IList<Fruit> fruits = apples;
fruits.Add(new Banana());
so I'm not sure why it compiled for you.
The IEnumerable<out T>
interface can be covariant (and it is in .NET 4.0 and later), because IEnumerable
only supports reading elements from a collection.
The Scala language has a similar concept of covariant and contravariant objects, and the chapter from Programming in Scala that discusses generics should serve as a good introduction to covariance in C# as well.
Have a look at this article for an explanation of Covariance and Contravariance.
http://msdn.microsoft.com/en-us/library/dd799517.aspx
The CLR already had some support for variance in generic types and with c# 4 comes the syntax to use this. With generic variance the variance is applied to the type parameters of the interfaces and delegate types.
Covariance is about being able to treat a returned value as a more general type, and is possible when the interface method(s) only return that type. In this example the derived interface instance can be reassigned as the base, but not the other way around.
public interface ISomeInterfaceWithOut<out T>
{
T GetSomething();
}
ISomeInterfaceWithOut<Apple> b = new Blah<Apple>();
ISomeInterfaceWithOut<Fruit> fruit = b;
Contravariance is about being able to treat a parameter type as a more specific type, and is possible when the interface method(s) only consume that type. In this example the base interface instance can be reassigned as the derived, but not the other way around.
public interface ISomeInterfaceWithIn<in T>
{
void SetSomething(T instance);
}
ISomeInterfaceWithIn<Fruit> b = new Blah<Fruit>();
ISomeInterfaceWithIn<Apple> apple = b;
Invariance is when the both cases are happening and the interface method(s) are both returning and consuming the type. Neither covariance or contravariance can apply. Here any usage like the above will not work, as the 'out T' covariance or 'in T' contravariance type parameter is not allowed to be defined as the methods contain both cases.
Consider this:
//it is not possible to declare 'out T' or 'in T' here - invalid variance
public interface ISomeInterface<T>
{
T GetSomething();
void SetSomething(T instance);
}
Neither of your examples will work as they are. Contravariance/covariance applies to interfaces and delegates which have had their generic type declared as 'in' / 'out', IList is invariant.
Since the IEnumerable<T>
interface is covariant from .NET 4, you can do this from 4 onwards but not 3.5. Using fruits as IList here will not work when declaring fruits - it is not covariant.
List<Apple> apples = new List<Apple>();
//List<Apple> apples implements IEnumerable<Apple>
IEnumerable<Fruit> fruits = apples;
Here is the definition of IEnumerable<T>
//Version=4.0.0.0
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
//Version=2.0.0.0
public interface IEnumerable<T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With