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.

More by Author

Must Read