Generics are a very powerful feature in the .NET 2 Framework. I use generics nearly every day. Adding a constraint to a generic is even more powerful. A generic constraint can either be a class or an interface. In most cases an interface is a better choice. An interface allows more freedom while maintaining strongly typed code. The example below will make generic constraints with an interface clear.
There is one base class, Animal and two derived classes, Dog and Parakeet. Warning: This code built using C# 3.0
public abstract class Animal
{
public abstract string AnimalType { get; }
public string Identifier { get; private set; }
public int AnnualCost { get; set; }
public readonly int NumberOfLegs;
public Animal(string identifier, int numberOfLegs)
{
Identifier = identifier;
NumberOfLegs = numberOfLegs;
}
}
public class Dog : Animal
{
private string _AnimalType;
public override string AnimalType
{
get { return _AnimalType; }
}
public Dog(string identifier)
: base(identifier, 4)
{
_AnimalType = "Dog";
}
}
public class Parakeet : Animal
{
private string _AnimalType;
public override string AnimalType
{
get { return _AnimalType; }
}
public Parakeet(string identifier)
: base(identifier, 2)
{
_AnimalType = "Parakeet";
}
}
Create a list of animals and print them out. Notice the class constraint, where T : Animal. This allows a foreach loop to be used on the generic list, List<T>.
static void Main(string[] args)
{
List<Animal> animalList = new List<Animal>();
animalList.Add(new Dog("Fido") { AnnualCost = 5000 });
animalList.Add(new Parakeet("Polly") { AnnualCost = 2000 });
animalList.Add(new Dog("Rex") { AnnualCost = 15000 });
PrintList(animalList);
Console.ReadLine();
}
public static void PrintList<T>(List<T> myList)
where T : Animal
{
foreach (Animal item in myList)
{
Console.WriteLine("Item Identifier {0}, Type {1}, Cost {2}",
item.Identifier, item.AnimalType, item.AnnualCost);
}
}
There is another class tree with a base class of Vehicle and two derived classes, Van and Motorcycle. My PrintList method is almost what is needed. Instead of creating another method to work on a different base class, an interface is used to allow the PrintList method to handle any class that implements the interface.
The interface and the changes to the Animal class to implement the interface are shown below.
public interface IAnyObject
{
string ObjectType { get; }
string Identifier { get; }
int AnnualCost { get; set; }
}
public abstract class Animal : IAnyObject
{
public string ObjectType { get { return AnimalType; } }
public abstract string AnimalType { get; }
public string Identifier { get; private set; }
public int AnnualCost { get; set; }
public readonly int NumberOfLegs;
public Animal(string identifier, int numberOfLegs)
{
Identifier = identifier;
NumberOfLegs = numberOfLegs;
}
}
Only minor changes in the PrintList method are required to use the IAnyObject interface instead of the Animal class.
public static void PrintList<T>(List<T> myList)
where T : IAnyObject
{
foreach (IAnyObject item in myList)
{
Console.WriteLine("Item Identifier {0}, Type {1}, Cost {2}",
item.Identifier, item.ObjectType, item.AnnualCost);
}
}
Now, the PrintList method can be used by a larger range of lists. The code below shows three different lists.
static void Main(string[] args)
{
List<Animal> animalList = new List<Animal>();
animalList.Add(new Dog("Fido") { AnnualCost = 5000 });
animalList.Add(new Parakeet("Polly") { AnnualCost = 2000 });
animalList.Add(new Dog("Rex") { AnnualCost = 15000 });
PrintList(animalList);
List<Vehicle> vehicleList = new List<Vehicle>();
vehicleList.Add(new Van("Mom's Taxi") { AnnualCost = 25000 });
vehicleList.Add(new Motorcycle("Dad's Toy") { AnnualCost = 13000 });
PrintList(vehicleList);
// Even this is allowed.
List<IAnyObject> anyList = new List<IAnyObject>();
anyList.Add(new Dog("Lassie") { AnnualCost = 1234 });
anyList.Add(new Motorcycle("Hog Heaven") { AnnualCost = 23000 });
PrintList(anyList);
Console.ReadLine();
}
Download source files - 6.7 Kb