Variance has to do with types of parameters and return values. Covariance means that an instance of a subclass can be used when an instance of a parent class is expected, while contravariance means that an instance of a superclass can be used when an instance of a subclass is expected. When neither is possible, it is called invariance.
All fourth-generation languages support some kind of variance. In C# 3.0 and earlier versions, parameters are covariant, and return types are contravariant. So, this works because string and integer parameters are covariant to object parameters:
public static void MessageToYou(object theMessage)
{
if (theMessage != null)
Console.Writeline(theMessage)
}
//then:
MessageToYou("It's a message, yay!");
MessageToYou(4+6.6);
And this works because object return types are contravariant to string and integer return types (for example):
object theMessage = MethodThatGetsTheMessage();
Generics are invariant in C# 2.0 and 3.0. This means that if Basket<apple>
is of type Basket<fruit>
, those Baskets
are not interchangeable as strings and objects are in the preceding example.
Variance
If you look at a method like the following one:public static void WriteMessages()
{
List<string> someMessages = new List<string>();
someMessages.Add("The first message");
someMessages.Add("The second message");
MessagesToYou(someMessages);
}
and then you try to call that method with a string type
//This doesn't work in C#3!!
public static void MessagesToYou(IEnumerable<object> theMessages)
{
foreach (var item in theMessages)
Console.WriteLine(item);
}
it will fail. Generics are invariant in C# 3.0. But, in Visual Studio 2010 and later this complies because IEnumerable<T>
is covariant — you can use a more derived type as a substitute for a higher-order type.
Contravariance
A scheduling application could haveEvents
, which have a date, and then a set of subclasses, one of which is Course
. A Course
is an Event
. Courses know their own number of students. One of these methods is MakeCalendar
:public void MakeCalendar(IEnumerable<Event> theEvents)
{
foreach (Event item in theEvents)
{
Console.WriteLine(item.WhenItIs.ToString());
}
}
Pretend that it makes a calendar. For now, all it does is print the date to the console. MakeCalendar
is systemwide, so it expects some enumerable list of events.
The application also has a sort algorithm at the main system called EventSorter
that passes a sorted collection into the Sort
method. It expects a call from a list of Events. Here is the EventSorter
class:
class EventSorter : IComparer<Event>
{
public int Compare(Event x, Event y)
{
return x.WhenItIs.CompareTo(y.WhenItIs);
}
}
The event manager makes a list of courses, sorts them, and then makes a calendar. ScheduleCourses
creates the list of courses and then calls courses.Sort()
with EventSorter
as an argument, as shown here:
public void ScheduleCourses()
{
List<Course> courses = new List<Course>()
{
new Course(){NumberOfStudents=20,
WhenItIs = new DateTime(2018,2,1)},
new Course(){NumberOfStudents=14,
WhenItIs = new DateTime(2018,3,1)},
new Course(){NumberOfStudents=24,
WhenItIs = new DateTime(2018,4,1)},
};
//Pass an ICompare<Event> class to the List<Course> collection.
//It should be an ICompare<Course>, but it can use ICompare<Event>
// because of contravariance
courses.Sort(new EventSorter());
//Pass a List of courses, where a List of Events was expected.
//We can do this because generic parameters are covariant
MakeCalendar(courses);
}
But wait, this is a list of courses that calls Sort()
, not a list of events. Doesn't matter — IComparer<Event>
is a contravariant generic for T (its return type) as compared to IComparer<Course>
, so it’s still possible to use the algorithm.
Now the application passes a list into the MakeSchedule
method, but that method expects an enumerable collection of Events
. Because parameters are covariant for generics now, it’s possible to pass in a List
of courses
, as Course
is covariant to Event.
There is another example of contravariance, using parameters rather than return values. If you have a method that returns a generic list of courses
, you can call that method expecting a list of Events
, because Event
is a superclass of Course
.
You know how you can have a method that returns a String
and assign the return value to a variable that you have declared an object? Now you can do that with a generic collection, too.
In general, the C# compiler makes assumptions about the generic type conversion. As long as you’re working up the chain for parameters or down the chain for return types, C# will just magically figure the type out.
Covariance
The application now passes the list into theMakeSchedule
method, but that method expects an enumerable collection of Events
. Because parameters are covariant for generics now, it’s possible to pass in a List
of courses
, as Course
is covariant to Event
. This is covariance for parameters.