Share via


Visual Studio 2010: Covariance and Contravariance - In search of better examples…

image3

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.

image_thumb1

Here is what I’m running. Are you?

image

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.

image_thumb152

 

image

Here is some naughty code….

Here is an unfortunate thing. The compiler didn’t catch it, so it hit the runtime.image

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…”

image

A parameter is covariant if you can use a more derived type as a substitute for the formal parameter type. So this example is not covariant.

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)
    2:  {
    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)
    9:      {
   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.

 image

Imagine a scenario where you have an array like this:

 image

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

image You intuitively know that you can pass a derived class object to any method expecting a base class object. You intuitively know that you can pass a derived object to any method expecting a base object. After all, the derived object is also an instance of the base object. You instinctively know that you can store the result of a method in a variable of a less-derived object type than the formal method return type.

Bill Wagner’s Excellent Article

https://visualstudiomagazine.com/articles/2009/05/01/generic-covariance-and-contravariance-in-c-40.aspximage