Virtual Developer Workshop: Containerized Development with Docker
A kiss on the lips may be quite continental but diamonds are a girl's best friend.
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.
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.
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 ClassListing 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.