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);
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);
History
- 2013-02-23: Initial post.