With the advent of .NET 2.0, Generics is undoubtedly the single most important language enhancement. However, to use generics to the full potential, developers should understand both the capabilities and limitations of generics as they relate specifically to polymorphism. In short, while generics do support some polymorphic behavior, the use of Interfaces should still be the preferred polymorphic mechanism in many cases.
First, an example of polymorphic capabilities,
abstract class Person { public string FirstName; public string LastName; public void Speak(); } class Fireman : Person { public int NumFiresExtinguished; } class Policeman : Person { public int NumArrestsMade; }
Using System.Collections.Generics.List, one could consume like this:
List<Person> list = new List<Person>(); list.Add(new Fireman()); list.Add(new Policeman());
Since both Fireman and Policeman inherit from Person, this compiles just fine.
Further, with constraints we can define our own generic sub-classes:
class PersonAction where T : Person { public void DoAction(T item); }
Like the example above, we can consume like this:
PersonAction<Person> personAction = new PersonAction<Person>(); personAction.DoAction(new Fireman()); personAction.DoAction(new Policeman()); PersonAction<Fireman> fireAction = new PersonAction<Fireman>(); fireAction.DoAction(new Fireman()); //fireAction.DoAction(new Policeman()); //<--Does not compile which is good
So far, everything is as we expect. However, if we try to push generic polymorphic behavior one step further, this is where things gets interesting.
List<PersonAction<Person>> personActionList = new List<PersonAction<Person>>(); //personActionList.Add(new PersonAction()); //<--Does not compile //personActionList.Add(new PersonAction()); //<--Does not compile
One would think that these two previous statements would be legal. After all, we specified that the PersonAction must be and PersonAction certainly seems to satisfy this at first glance. However, the compiler does not allow this because when we use generics in this way, the compiler is expecting the exact type – Person.
The good news is that this situation is easily rectified by using Interfaces.
interface IAction { void DoAction(Person item); }
And we'll change PersonAction to:
class PersonAction : IAction where T : Person { public void DoAction(T item); public void DoAction(Person item) { this.DoAction((T)item); } }
Now we can simply do this for polymorphic behavior with generic classes:
List<IAction> personActionList2 = new List<IAction>(); personActionList2.Add(new PersonAction<Fireman>()); personActionList2.Add(new PersonAction<Policeman>());
While there is no doubt that generics is one of the most important language enhancements we've seen in a while, there are many situation where traditional OOP with Interfaces should be the preferred implementation to support generics.