Self-Referencing Generics: Follow-up
After being prompted by a friend (or ex-colleague, or drinking buddy, whatever you want to be called J) I think I owe further explanation about my previous post on generics.
Consider these two classes;
public class FirstClass<T>
{
public FirstClass<T> Parent;
}
public class SecondClass<T>
{
public T Parent;
}
Now, if I subclass each of these classes to shape the two approaches (First is the common way to do things, Second is using a self-referencing generic);
public class FirstSubClass : FirstClass<string> { }
public class SecondSubClass : SecondClass<SecondSubClass> { }
... the way in which we can use these is very different. Taking the First example, what do you think to the following code?
1: FirstSubClass subclass = new FirstSubClass();
2: FirstClass<string> parent = subclass.Parent;
3: FirstSubClass parent2 = subclass.Parent;
I’ve added line numbers for clarity. As you can see, Line 2 is valid, but Line 3 won’t compile. This means my “Parent” property is strongly typed at the base class, and if I need an instance of the sub class I must manually cast it. This is absolutely what you want sometimes, as when defining a typical Object Oriented inheritance hierarchy it makes sense – in fact, this is polymorphism in action.
Now consider the code for our Second example;
1: SecondSubClass subclass = new SecondSubClass();
2: SecondClass<SecondSubClass> parent = subclass.Parent;
3: SecondSubClass parent2 = subclass.Parent;
With this approach, our Parent property is strongly typed as the subclass, which makes all of the statements above compile.
This is particularly useful when you want to have navigable chains of objects (such as my linked list example) or to strongly type as the implementing class, not the base class. What we’re effectively doing is delaying the declaration of a type, and avoiding polymorphism because we know our consumers will always want an instance of the subclass.
Keen-eyed readers may have noticed the two classes are not equivalent, as one obviously holds a string, and the other cannot. To prove it is possible;
public class ThirdClass<T, U>
{
public T Parent;
public U Value;
}
public class ThirdSubClass : ThirdClass<ThirdSubClass, string> { }
Hey presto, a new base and subclass that expose a string value and a Parent.
Example Please!
OK, so a simple example is warranted perhaps.
One of my pet hates is ICloneable – it returns an object, and I wish it was strongly typed. Well, here’s another version of ICloneable;
public interface ICloneable<T>
{
T Clone();
}
Sure, I know this is an interface not a base class, but the concept is the same. Anyway, what this means is that I can implement my new ICloneable<T> in a class;
public class CloneableClass : ICloneable<CloneableClass>
{
public CloneableClass Clone()
{
// really basic implementation!
return this.MemberwiseClone() as CloneableClass;
}
}
... and hey presto, I have a strongly typed implementation of cloning.
Problems
As with all seemingly nice answers to little problems, there are some gotchas, so exercise caution.
1. I can’t use a base class of ICloneable<object> to refer to my ICloneable<T> implementers – this just doesn’t work with generics.
2. In all of these examples, you are effectively tightly coupling the base class and subclass. The base class dictates exactly how a subclass must use it. For example, what if a class implements ICloneable<string>? This will compile and work, but is semantically different to what I intended, and could confuse the socks off a development team! Lesson: if you’re going to use this, consider doing it internal to your library – be careful about using it in a public API!
3. I wasn’t really condoning the “object.Next.Next.Next.Previous.Age” coding approach I used in my last post; it was merely to show that it was possible, and that the navigation properties and value properties now exist on the same type. Please don’t use lines of code like that or the rest of my team will come to get me when they're doing code reviews... J
Comments
Anonymous
June 16, 2008
Thanks for that, and 'friend' is fine by me. I usually refer to you as 'this really clever guy I know who went to work for Microsoft'. :o)Anonymous
June 16, 2008
I think you have me confused with someone else :-)