This took me some time to understand even simple examples of covariance and contravariance. If you have great samples, please let me post them.
Email me at bterkaly@microsoft.com (Bruno Terkaly).
Don’t forget to grab the code above.
Here is what I’m running. Are you?
If you haven’t done it yet, please start it now
Yes, to maximize the use of this blog, you should follow along. Learn how to install Visual Studio, then do it.
Click Watch the video to install VS 2010.
Here is some naughty code….
Before we start, Here is the class hierarchy of people, employees, etc
1: public class Person
2: {
3: public Person(string fName, string lName)
4: {
5: this.firstName = fName;
6: this.lastName = lName;
7: }
8: public virtual void Show()
9: {
10: Trace.WriteLine("Name is " + firstName + " " + lastName);
11: }
12: public string firstName;
13: public string lastName;
14: }
15: public class Employee : Person
16: {
17: public Employee(string title, string fName, string lName)
18: : base(fName, lName)
19: {
20: this.title = title;
21: }
22: public override void Show()
23: {
24: base.Show();
25: Trace.WriteLine("Title is " + title);
26: }
27: public string title;
28: }
29: public class Manager : Person
30: {
31: public Manager(int numberEmployees, string fName, string lName)
32: : base(fName, lName)
33: {
34: this.numberEmployees = numberEmployees;
35: }
36: public int numberEmployees;
37: }
38: //
39: //Make the class People enumerable on Person
40: //
41: public class People : IEnumerable<Person>
42: {
43: private Person[] people;
44: public People(Person[] pArray)
45: {
46: people = new Person[pArray.Length];
47: for (int i = 0; i < pArray.Length; i++)
48: {
49: people[i] = pArray[i];
50: }
51: }
52: //This enumerator returns a list of person objects,
53: //one at a time.
54: public IEnumerator<Person> GetEnumerator()
55: {
56: foreach (Person p in people)
57: {
58: yield return p;
59: }
60: }
61: // Needed for to make compiler happy, but not used in my code.
62: IEnumerator IEnumerable.GetEnumerator()
63: {
64: foreach (Person p in people)
65: {
66: yield return p;
67: }
68: }
69: }
Test your knowledge: Why doesn’t this compile?
The answer is always the same with compiler writers – “because you can get into trouble…”
It is invariant because you must use the exact match of the formal type name.
Object-Based Collections
Since .NET 1.x we've struggled with collections (ArrayList, HashTable, Queue and so on).
listOfThings could really be Employee objects, and all this code would work.
1: private void SafeCovariance(ArrayList listOfThings)
3: foreach(object o in listOfThings)
4: Console.WriteLine(o);
5:
6: int begin = 0;
7: int stop = listOfThings.Count - 1;
8: while (begin < end)
10: object tmp = listOfThings[begin];
11: listOfThings[begin] = listOfThings[end];
12: listOfThings[end] = tmp;
13: begin++;
14: end--;
15: }
16:
17: foreach(object o in listOfThings)
18: Console.WriteLine(o);
19: }
20:
What about contravariant?
A return value is contravariant if you can assign the return type to a variable of a less derived type than the formal parameter.
Why isn’t the code above covariant?
This code looks like it should succeed because a Employee is type of Person, and one should always be able to assign a more derived type of object, such as a Employee, to a base class reference, such as a Person. This is fixed in C# 4.0.
This code should compile in C# 3.0, but does not, because C# currently does not support "Declaration Side Covariance for Interface and Delegate Types," according to Anders Hejlsberg.
This is needed because we don't know if we are going to add a "SubContractor," which might also be derived class of a person.
Imagine a scenario where you have an array like this:
Imagine loop through this array and trying to access the "Salary." Everything works fine until you get to the "SubContractor," where "Hours" and "HourlyRate" are the data elements. We'd get a crash.
That is what the type system is protecting us from.
The C# 4.0 Solution
The "out" keyword
The new feature being added in Visual Studio 2010 ensures that this assignment will work if you make one minor change to the declaration for your delegate. In particular, you need to use the keyword out in your type parameter:
delegate T MyFunction<out T>();
Bill Wagner’s Excellent Article