Chapter 4. Revising Generics

In This Chapter

  • Understanding variance

  • Working with contravariance

  • Using covariance

Generics are covered in length in Books I and II, as they relate to creating collections of objects or business concepts, and how they impact object-oriented programming. They also play a large role in dynamic design and programming, which Chapter 1 of this book covers.

The generics model implemented in C# 2.0 was incomplete. Although parameters in C# all allow for variance in several directions, generics do not.

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 contravarient. 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 contravarient to string and integer return types (for example):

object theMessage = MethodThatGetsTheMessage();

Generics are nonvariant in C# 2.0 and 3.0. This means that if Basket<apple> is of type Basket<fruit>, those Baskets are not interchangeable like strings and objects are in the preceding example.

Variance

If we look at a method like the preceding 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 we try to call that method like we did earlier 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);
}

this fails in Visual Studio 2008. Generics are invariant in C# 3.0. But, in Visual Studio 2010 this complies because IEnumerable<T> is covariant — you can use a more derived type as a substitute for a higher-order type. Let's look at a real example.

Contravariance

In my scheduling application, I have Events, 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.

Anyway, back at the ranch, I have a method called MakeCalendar.

public void MakeCalendar(IEnumerable<Event> theEvents)
{
    foreach (Event item in theEvents)
    {
        Console.WriteLine(item.WhenItIs.ToString());
    }
}

Pretend 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.

I also have a Sort algorithm at the main system, called EventSorter. This is used to pass into the Sort method of collections. It expects to be called 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);
    }
}

I am writing the Instructor Led Training section of the event manager, and I need to make a list of courses, sort them, and then make a calendar. So I make my list of courses in ScheduleCourses, then I call sort and pass in the EventSorter:

public void ScheduleCourses()
{
    List<Course> courses = new List<Course>()
    {
        new Course(){NumberOfStudents=20, WhenItIs = new
   DateTime(2009,2,1)},
        new Course(){NumberOfStudents=14, WhenItIs = new
   DateTime(2009,3,1)},
        new Course(){NumberOfStudents=24, WhenItIs = new
   DateTime(2009,4,1)},
    };
    //Now I am passing an ICompare<Event> class to my
   List<Course> collection.
    //It should be an ICompare<Course> but I can use
   ICompare<Event> because of contravariance
    courses.Sort(new EventSorter());

    //I am passing 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 I am calling Sort from, right, 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 I can still use the algorithm.

Now I have to pass my list into the MakeSchedule method, but that method expects an enumerable collection of Events. Because parameters are covariant for generics now, I can pass in a List of Courses, as Course is covariant to Event. Make sense?

There is another example of contravariance, using parameters rather than return values. If I have a method that returns a generic list of Courses, I 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 are working up the chain for parameters, or down the chain for return types, C# will just magically figure the type out.

Covariance

I now have to pass my list into the MakeSchedule method, but that method expects an enumerable collection of Events. Because parameters are covariant for generics now, I can pass in a List of Courses, as Course is covariant to Event. This is covariance for parameters.

..................Content has been hidden....................

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