For instance,
1: IEnumerable<string> strings = new List<string>();
The assignment compatibility is preserved. Know that:
Invariant means, must use the exact type – be ‘in’ or ‘out’
Covariant means, parameter type can be of a derived class type; upcast’able; more specific to more general. Specified as output types: return types, 'out' params, get properties. Array types fall in this category.
Contravariant is opposite of covariance. Parameter type can be a parent class type; downcast'able; more general to specific type. Use base type where a child type was expected. Specified as input types: set, 'in' parameters, etc.
Covariance and contravariance (or simply “variance”) are advanced concepts. The motivation behind introducing and enhancing variance in C# was to allow generic interface and generic types (in particular, those defined in the Framework, such as IEnumerable<T>() to work more as we would expect.
Conventionally, array types are covariant. This means that X can be cast to Y if X subclasses Y. For instance:
1: Bear[] bears = new Bear[3];
2: Animal[] animals = bears; // OK
The downside of this reusability is that element assignments can fail at runtime:
1: animals[0] = new Camel(); // Runtime error. Why, because Animal class is
2: //expecting Bears, notCamels!
Covariance (and contra variance) in interfaces is something that you typically consume: it’s less common that you need to write variant interfaces.
See following example:
1: //Base type
2: class Car { public string name = "Base car"; }
3:
4: //Sub type
5: class Honda : Car { public Honda() { name = "Specific car:
6: Honda"; } }
7:
8: class Program
9: {
10: delegate T Func<out T>(); /*Covariant; remember? Specified as 'out'put types*/
11:
12: delegate void Action<in T>(T a);/*Contravariant; Specified as 'in'put types!*/
13: static void Main(string[]
14: args)
15: {
16: // Example: Covariance
17: Func<Honda> child1 = () => new Honda();
18: Func<Car> parent1 = child1; //Assignment compatibility is preserved
19:
20: // Example: Contravariance
21: Action<Car> parent2 = (someArgs) => { Console.WriteLine(someArgs.name); };
22: Action<Honda> child2 = parent2;//Assignment compatibility is maintained
23: child2(new Honda());//Prints, Specific car: Honda
24:
25: Console.ReadKey();
26: }
27: }
The type system is free to allow us to safely upcast and downcast between those interfaces in the direction indicated by the keyword. You might like exploring more here.
It's really not a "save the day" sort of feature. It's more of a "it works the way I expect it to" feature - Eric LippertEnjoy, variants in life! (0: