Understanding Covariance and Contravariance

Introduction

A kiss on the lips may be quite continental but diamonds are a girl's best friend.
--Jule Styn

ENIAC, short for Electronic Numerical Integrator And Computer, was the first general-purpose computer. ENIAC was developed to calculate artillery firing information. That's ironic in a way because that seemed to be what we were learning to do in the differential equations course I took at Michigan State University almost 50 years later. The ENIAC project began at the University of Pennsylvania in 1943, and I took differential equations in the early 1990s after a stint in the Army.

ENIAC is attributed to Presper Eckert and John Mauchly, but interestingly--borrowing from Wikipedia--John Von Neumann became aware of the project while working on the Manhattan Project in Los Alamos and the first test problems run were computations for the hydrogen bomb not general artillery calculations, and women were the primary programmers of this first computer. (I will have to double check these facts, but besides Wikipedia I recall this anecdotal information being relayed to me by some of MSU's great computer science teachers.) A further irony is that so few women seem to pursue computer science as a profession these days.

What was common and still seems to be common today is that mathematics and physics studies seem to breed a lot of computer scientists and ideas prevalent or introduced even now are heavily influenced by mathematics. Lambda expressions are based on Lambda Calculus, and covariance and contravariance are mathematical terms. Mathematically covariance is a measure of how much things vary together, and contravariance is the opposite of covariance. Of course, if you search the Web, the descriptions, the mathematics, and the applications of these terms can seem bewildering. For our part it is useful to know how they are applied to .NET framework. The three basic concepts discussed in this article are assignment compatibility, covariance, and contravariance. Examples are included to show you how they are applied in .NET framework 4.0.

Understanding Assignment Compatibility

In .NET there is the concept of assignment compatibility. Assignment compatibility is the ability to assign one type to another, usually a child type to a parent type, and assignment compatibility is a big part of what makes polymorphism work.

Suppose for example you have a typical class hierarchy consisting of Person, Employee, Customer, and Manager classes. Employee and Customer inherit from Person and Manager inherits from Employee. Assignment compatibility supports the intuitive assignment of Employee, Customer, or Manager to a Person variable and invoking a member on Person actually invokes the member on the instantiated class. This behavior is intuitive. In past version of .NET--prior to 4.0--things got hinky when you tried to assign things like generic delegates with parameters of a narrower type to delegates declared with a narrower type, covariance extends assignment compatibility into these areas. And, of course, if you declared something like a generic delegate with a narrower type, like Employee, and tried to assign it a generic delegate with a wider type it didn't work. Support for this direction of assignment compatibility is contravariance.

Collectively variance--covariance and contravariance--were implemented to extended and preserve or reverse assignment compatibility into generics, collections, and interfaces.

Understanding Covariance

I mistakenly started this article by limiting the discussion in such a way that I was really talking about assignment compatibility. Fortunately, I wasn't sure and Microsoft alums Charlie Calvert and Eric Lippert set me straight (or straighter). If I still haven't perfected an explanation then the fault is my own. However, with many of the blogs, white papers, and even MSDN help focusing on the "wider to narrower" and "narrower to wider" assignment bits the subject seems a little abstruse.

Rather than go into the math, you can read Eric Lippert's blog for more on that subject, let's continue our example using the Person hierarchy and an exemplar to illustrate the mechanics of support for variance in .NET 4.0. Keep in mind that variance is not limited to generic delegates; it is extended into other things like interfaces. I have simply elected to demonstrate a technical aspect for practical purposes rather than continue an abstract discussion of the subject.

Suppose there is an operation you want to perform on an Employee object and you do it through a Person variable. You would intuitively declare a Person and initialize an instance of Employee.

Dim p as Person = New Employee()

If Customer inherits from Person then this is intuitive. For example, you might want to create an employee report using a collection of employees that validly might contain instances of Employee and Manager objects. Now suppose you wanted to create the Employee/Manager object through a function that determined which kind of object to create based on some logic like a database field. You could use a generic delegate for the construction operation (and we'll use a Lambda Expression for brevity; just keep in mind that Lambda Expressions are just short functions). You might write the following code-see Listing 1:

Module Module1

  Delegate Function MyFunction(Of T)() As T


    Sub Main()

      ' doesn't work pre .NET 4.0 and doesn't work as defined 
      Dim getEmployee As MyFunction(Of Employee) = Function() New Employee()
      Dim getPerson As MyFunction(Of Person) = getEmployee

      Console.ReadLine()

    End Sub
End Module

Class Person

End Class

Class Customer
  Inherits Person
End Class

Class Employee
  Inherits Person
End Class

Class Manager
  Inherits Employee

End Class
Listing 1: An Employee is a Person so it would seem that the following code would work as defined; it doesn't.



Suppose for argument sake that getEmployee contained logic to determine if a Manager or Employee object was constructed, although clearly it doesn't. Since an Employee (and Manager) descend from Person it seems intuitive that the code in Listing 1 will work. That is that getPerson can be assigned getEmployee, covariance, because Employee and Manager are narrower types than Person assignment, compatibility should be supported. Prior to .NET framework 4.0 it wasn't (in this scenario) and as defined above it is not.



Understanding Covariance and Contravariance

To make the code compile you need to change the parameter T in MyFunction to an Out parameter. The code will compile and work because using the Out modifier makes the generic delegate MyFunction covariant-see Listing 2.

Module Module1

  Delegate Function MyFunction(Of Out T)() As T

    Sub Main()

      ' covariance
      Dim getEmployee As MyFunction(Of Employee) = Function() New Employee()
      Dim getPerson As MyFunction(Of Person) = getEmployee

      Console.ReadLine()

    End Sub

End Module

Class Person

End Class

Class Customer
  Inherits Person
End Class

Class Employee
  Inherits Person
End Class

Class Manager
  Inherits Employee

End Class
Listing 2: Changing the generic parameter T to an Out parameter in effect turns on covariance, allowing the assignment of the narrower type parameter Employee to a wider type parameter Person.



With this minor revision assignment compatibility from the narrower (child) type of Employee to the wider (parent) type Person is preserved.

Contravriance in essence reverses the assignment compatibility and extends it, so that a wider type can be assigned to a narrower type.

Understanding Contravariance

If assignment compatibility is both covariant and contravariant then it is said to be invariant. Covariance as demonstrated, preserves assignment compatibility into areas where it previously was unsupported. Contravariance reverses the direction of compatibility and extends it into areas where prior to .NET framework 4.0 it didn't exist.

Again if you just define a generic delegate in the usual manner and declare an instance of the wider type parameter and then assign that to an instance of the delegate with a narrower type parameter, you would think it would compile. It won't without a change. Listing 3 shows the wrong way to define contravariance, and Listing 4 shows that the In modifier permits the mechanics work.

Module Module1

  Delegate Sub MyAction(Of T)(ByVal parm As T)

    Sub Main()

      ' not quite contravariance
      Dim printPerson As MyAction(Of Person) = Sub(person) Console.WriteLine(person)
      Dim printEmployee As MyAction(Of Employee) = printPerson


      Console.ReadLine()

    End Sub

End Module

Class Person

End Class

Class Customer
  Inherits Person
End Class

Class Employee
  Inherits Person
End Class

Class Manager
  Inherits Employee

End Class
Listing 3: Ooops! Not quite contravariant.



Module Module1

  Delegate Sub MyAction(Of In T)(ByVal parm As T)


    Sub Main()

      ' contravariance
      Dim printPerson As MyAction(Of Person) = Sub(person) Console.WriteLine(person)
      Dim printEmployee As MyAction(Of Employee) = printPerson
      Console.ReadLine()
    End Sub

End Module

Class Person

End Class

Class Customer
  Inherits Person
End Class

Class Employee
  Inherits Person
End Class

Class Manager
  Inherits Employee

End Class
Listing 4: Changing the generic parameter to use the In modifier and contravariance is supported.



By changing the generic parameter to use the In modifier reverse assignment compatibility is supported.

In a general sense think of this as operations on Person may be useful to extend to child types like Employee. (The use of Lambda Expressions is not relevant.) The compiler also does a good job of making sure that the contravariant assignment is permissible. For example, assigning a delegate that performs an operation on Employee to a delegate for a Customer won't compile because Customers are Person (objects) but not Employee objects.

Summary

Assignment compatibility is the ability to assign variables of one type to instances of another. Covariance preserves assignment compatibility from children to parents (narrower to wider), and contravariance reverses assignment compatibility, supporting assignment of parent types to child types (wider to narrower types).

For the math concepts check out Eric Lippert's blog, and look into variance for interfaces, delegates, and generics for more information on the subject.

Resources

http://blogs.msdn.com/b/ericlippert/archive/2009/11/30/what-s-the-difference-between-covariance-and-assignment-compatibility.aspx?wa=wsignin1.0





About the Author

Paul Kimmel

Paul Kimmel is the VB Today columnist for CodeGuru and has written several books on object-oriented programming and .NET. Check out his upcoming book Professional DevExpress ASP.NET Controls (from Wiley) now available on Amazon.com and fine bookstores everywhere. Look for his upcoming book Teach Yourself the ADO.NET Entity Framework in 24 Hours (from Sams). You may contact him for technology questions at pkimmel@softconcepts .com. Paul Kimmel is a Technical Evangelist for Developer Express, Inc, and you can ask him about Developer Express at paulk@devexpress.com and read his DX blog at http:// community.devexpress.com/blogs/paulk.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • New IT trends to support worker mobility — such as VDI and BYOD — are quickly gaining interest and adoption. But just as with any new trend, there are concerns and pitfalls to avoid.  Download this paper to learn the most important considerations to keep in mind for your VDI project.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds