Click here to Skip to main content
15,881,938 members
Articles / Programming Languages / C#
Tip/Trick

Supporting Contravariance in Generic Types, .NET 2.0

Rate me:
Please Sign up or sign in to vote.
4.00/5 (1 vote)
8 Apr 2013CPOL1 min read 10.5K   4  
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.

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

And a concrete class Cat derived from class Animal.

C#
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>.

C#
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>.

C#
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>.

C#
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.

C#
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.

C#
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.

C#
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
China China
I am now programming applications for the Internet of Things.

Comments and Discussions

 
-- There are no messages in this forum --