Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Supporting Contravariance in Generic Types, .NET 2.0

0.00/5 (No votes)
8 Apr 2013 1  
Generic Lists or Generic Types contravariance without casting.

Introduction

Generic types in .NET 2.0 have given us great power of reusing algorithms. However, we will probably meet with some difficulty when we are using Generic Lists or other generic types: List<ConcreteType> could not be converted into List<GenericType> naturally even though ConcreteType is a subclass of GenericType.

This article will talk about how to surmount this with generic methods.

Background

To explain the problem, please take a look at the following simple example. Currently we have a base class Animal.

abstract class Animal
{
    public string Name { get; protected set; }
    public abstract void Speak ();
}

And a concrete class Cat derived from class Animal.

class Cat : Animal
{
    public Cat (string name) {
        base.Name = name;
    }
    public override void Speak () {
        Console.WriteLine (String.Concat ("I am ", this.Name, ". Meow..."));
    }
}

And a class CatHouse which contains a property Cats of type List<Cat>.

class CatHouse
{
    public List<Cat> Cats { get; private set; }
    public CatHouse () {
        this.Cats = new List<Cat> ();
    }
}

Now we have a method that takes a generic list type List<Animal>.

public static void AllSpeak (List<Animal> animals) {
    Console.WriteLine ("Greeting from " + animals.Count + " animals.");
    foreach (var animal in animals) {
        animal.Speak ();
    }
}

If you want to use the CatHouse.Cats property directly with the AllSpeak function, the compiler will complain that the type of the animals argument is a mismatch, since List<Cat> is not List<Animal>.

var catHouse = new CatHouse ();
catHouse.Cats.Add (new Cat ("Kitty"));
catHouse.Cats.Add (new Cat ("Rose"));
catHouse.Cats.Add (new Cat ("Jack"));

AllSpeak (catHouse.Cats); // here the problem is raised

One solution is switching to .NET 4.0 which supports contravariance of Generic Types. Since we are still in the .NET 2.0 world, it appears that we have to use another list to hold generic animals, like the following code shows.

var animalHouse = catHouse.Cats.ConvertAll<Animal> ((cat) => { return cat as Animal; });
AllSpeak (animalHouse);

It is inefficient, obviously.

Solution

Generic methods now come to rescue! Once we have the code of AllSpeak method, we can change it into a Generic Method. Then the list casting issue will be avoided.

public static void AllSpeak<T> (List<T> animals) where T : Animal {
    Console.WriteLine ("Greeting from " + animals.Count + " animals.");
    foreach (var animal in animals) {
        animal.Speak ();
    }
}

Now the compiler will happily take catHouse.Cats as the parameter of AllSpeak<T>. You don't need to cast types in the Generic Lists any more.

var catHouse = new CatHouse ();
catHouse.Cats.Add (new Cat ("Kitty"));
catHouse.Cats.Add (new Cat ("Rose"));
catHouse.Cats.Add (new Cat ("Jack"));

AllSpeak<Cat> (catHouse.Cats); // or simply use AllSpeak (catHouse.Cats) 

History

  • 2013-02-23: Initial post.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here