One of the greatest additions to .NET 2.0 is Generics. The ability to have a code template that is type safe, yet not type specific, is a huge time saver when it comes to writing collections and working with code that is repeated over and over again. However, one of the greatest problems with Generics, is that they are not truely polymorphic. If you create a base class of type MyBaseClass<t>, or even an interface defined as IMyInterface<t>, .NET will not allow MyBaseClass or MyInterface to be passed around in code as a polymorphic type.
This problem stems from the .NET compilers that support generics (C# and VB, to name the two commone ones) not actually knowing what the type of the object is when it comes to Generics. There is no type safety in the base class or interface - type safety in Generics is created only when a specific object inherits/implements the generic object. For example:
public class MyObject: MyBaseClass<string>{ }
At this point, a type safe class called "MyObject" has been defined. That class is now strongly typed with a string, and any of the methods and properties found in the base of MyObject that are typed with the <t> generic type are now a string.
What happens when there is a need to strongly type a number of objects and pass them around as a polymorphic type? Nothing... the .NET compilers will not allow this to happen without knowing the specific type, or with at least another generic type specified. Its this last little bit of information that creates a some-what polymorphic use of Generics.
Most developers out there will tell you that you can get polymorphism into generics through the straight use of interfaces. However, what they don't mention, is that the interfaces declared for polymorphism cannot be a generics implementation. If generics are used to avoid typing methods and properties as "object", but the objects are forced to use a standard interface to make the generics object polymorphic, doesn't that defeat the purpose of generics?
public interface IMyObject{
object MyMethod();
}
public Class MyBaseObject<t>: IMyObject {
public object MyMethod(){
//do something here.
}
}
At this point, the IMyObject interface can be passed around as polymorphic, but it loses the compile time type safety of the MyBaseObject generics class.
There are at least two solutions to polymorphism with generics:
1) Don't use a generics base object. Steve Mitchelotti has a great example of exactly this, while using generics object and interfaces for coding the rest of the app.
2) Use a generics interface declaration, and always pass the strong type with the polymorphic methods.
For example, the IPolyGen interface
public interface IPolyGen<t> {
t SomeProperty { get; set; }
t Add(t param);
void DoSomething();
}
This is a simple, not so real-world example of a generic interface. It recieves a type and uses that type throughout the interface. Implement this interface into two seperate objects - one that strongly types the object, and another the continues the generics into the object level.
public class MyObject : IPolyGen<string> {
#region Vars
private string _someProp = string.Empty;
#endregion
#region IPolyGen<string> Members
string IPolyGen<string>.SomeProperty {
get {
return _someProp;
}
set {
_someProp = value;
}
}
string IPolyGen<string>.Add(string param) {
return _someProp + ": " + param;
}
void IPolyGen<string>.DoSomething() {
_someProp = "Do Something";
}
#endregion
}
and for the generics continuation object (base object)
public class MyGenericObject<t>: IPolyGen<t> {
#region Vars
private t _someProp;
private List<t> _list = new List<t>();
#endregion
#region IPolyGen<t> Members
t IPolyGen<t>.SomeProperty {
get {
return _someProp;
}
set {
_someProp = value;
}
}
t IPolyGen<t>.Add(t param) {
_list.Add(param);
return param;
}
void IPolyGen<t>.DoSomething() {
//...i guess we can something here... but i'm not real sure what!
}
#endregion
}
The key to using these objects polymorphically, is to continue passing the generics type throughout your code. For example, a method can be created to accept an object of type IPolyGen, provided that IPolyGen recieves a type in parameter definition. Under normal circumstances, a non-generic interface can simply be declared in the parameter list:
private void RunMe(IPolyGen param) {
}
However, since the IPolyGen interface is itself a Generics object, the compiler will not accept this code and will return errors
Error 1 Using the generic type 'PolymorphicGenerics.Interfaces.IPolyGen<t>' requires '1' type arguments
In order to implement a method that used IPolyGen as a parameter, the strong type must be specified. For example, a method could be declared as such:
private void RunMe(IPolyGen<string> param) {
}
The problem with this method, is that the only type of object that can be passed into it, is an object that specifically implements IPolyGen<string>. That essentially defeats most of the purpose of generics. However, generics are not limited to the class and interface scope - a method can also be declared with a generics type. This allows for a method to be written that continues the chain of generics.
private void RunMe<t>(IPolyGen<t> param, t obj) {
}
The issue we face now, is that the type being passed into the generic method must be known at design time. However, the type being passed in can vary at every call. For example:
private void TestRunMe(){
MyObject o1 = new MyObject();
RunMe<string>(o1, "Test?");
MyGenericObject<DateTime> o2 = new MyGenericObject<DateTime>();
DateTime myDate = DateTime.Now;
RunMe<DateTime>(o2, myDate);
MyGenericObject<int> o3 = new MyGenericObject<int>();
RunMe<int>(o3, 999);
}
Here, the RunMe generic method is being called several times, with several different types. The IPolyGen object was used to create the MyGenericObject as a generics object, and was used to create the MyObject strongly typed object. Each of these is then passed into the RunMe method, along with the specific type of the generics object. This creates at least a limited ammount of polymorphism with generics.
More research needs to be done with this, to see what the limitations really are, but it appears to at least bring a small level of polymorphism into the generics world. This, in combination with the work of Scott Mitchelotti, gives new hope for the world of generics in .NET.