21.13 (Optional) Covariance and Contravariance for Generic Types

C# supports covariance and contravariance of generic interface and delegate types. We’ll consider these concepts in the context of arrays, which have always been covariant and contravariant in C#.

Covariance in Arrays

Recall our Employee class hierarchy from Section 12.5, which consisted of the base class Employee and the derived classes SalariedEmployee, HourlyEmployee, CommissionEmployee and BasePlusCommissionEmployee. Assuming the declarations


SalariedEmployee[] salariedEmployees = {
   new SalariedEmployee("Bob", "Blue", "111-11-1111", 800M),
   new SalariedEmployee("Rachel", "Red", "222-22-2222", 1234M) };
Employee[] employees;

we can write the following statement:


employees = salariedEmployees;

Even though the array type SalariedEmployee[] does not derive from the array type Employee[], the preceding assignment is allowed because class SalariedEmployee is a derived class of Employee.

Similarly, suppose we have the following method, which displays the string representation of each Employee in its employees array parameter:


void PrintEmployees(Employee[] employees)

We can call this method with the array of SalariedEmployees, as in


PrintEmployees(salariedEmployees);

and the method will correctly display the string representation of each SalariedEmployee object in the argument array. Assigning an array of a derived-class type to an array variable of a base-class type is an example of covariance.

Covariance in Generic Types

Covariance works with several generic interface and delegate types, including IEnumerable<T>. Arrays and generic collections implement the IEnumerable<T> interface. Using the salariedEmployees array declared previously, consider the following statement:


IEnumerable<Employee> employees = salariedEmployees;

In earlier versions of C#, this generated a compilation error. Interface IEnumerable<T> is now covariant, so the preceding statement is allowed. If we modify method PrintEmployees, as in


void PrintEmployees(IEnumerable<Employee> employees)

we can call PrintEmployees with the array of SalariedEmployee objects, because that array implements the interface IEnumerable<SalariedEmployee> and because a Salaried-Employee is an Employee and because IEnumerable<T> is covariant. Covariance like this works only with reference types that are related by a class hierarchy.

Contravariance in Arrays

Previously, we showed that an array of a derived-class type (salariedEmployees) can be assigned to an array variable of a base-class type (employees). Now, consider the following statement, which has always compiled in C#:


SalariedEmployee[] salariedEmployees2 =
   (SalariedEmployee[]) employees;

Based on the previous statements, we know that the Employee array variable employees currently refers to an array of SalariedEmployees. Using a cast operator to assign employees—an array of base-class-type elements—to salariedEmployees2—an array of derived-class-type elements—is an example of contravariance. The preceding cast will fail at runtime if employees is not an array of SalariedEmployees.

Contravariance in Generic Types

To understand contravariance in generic types, consider a SortedSet of SalariedEmployees. Class SortedSet<T> maintains a set of objects in sorted order—no duplicates are allowed. The objects placed in a SortedSet must implement the IComparable<T> interface. For classes that do not implement this interface, you can still compare their objects using an object that implements the IComparer<T> interface. This interface’s Compare method compares its two arguments and returns 0 if they’re equal, a negative integer if the first object is less than the second, or a positive integer if the first object is greater than the second.

Our Employee hierarchy classes do not implement IComparable<T>. Let’s assume we wish to sort Employees by social security number. We can implement the following class to compare any two Employees:


class EmployeeComparer : IComparer<Employee>
{
   int IComparer<Employee>.Compare(Employee a, Employee b)
   {
      return a.SocialSecurityNumber.CompareTo(
         b.SocialSecurityNumber);
   }
}

Method Compare returns the result of comparing the two Employees’ social security numbers using string method CompareTo.

Now consider the following statement, which creates a SortedSet:


SortedSet<SalariedEmployee> set =
   new SortedSet<SalariedEmployee>(new EmployeeComparer());

When the type argument does not implement IComparable<T>, you must supply an appropriate IComparer<T> object to compare the objects that will be placed in the Sorted-Set. Since, we’re creating a SortedSet of SalariedEmployees, the compiler expects the IComparer<T> object to implement the IComparer<SalariedEmployee>. Instead, we provided an object that implements IComparer<Employee>. The compiler allows us to provide an IComparer for a base-class type where an IComparer for a derived-class type is expected because interface IComparer<T> supports contravariance.

Web Resources

For a list of covariant and contravariant interface types, visit


https://msdn.microsoft.com/library/dd799517#VariantList

It’s also possible to create your own types that support covariance and contravariance. For information, visit


https://msdn.microsoft.com/library/mt654058
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset