More comments on generics
Here are some more unsorted thoughts on generics to continue this post (which has some interesting comments too).
Preserving types with generic type inference
To reiterate, instead of having a method like:
void Process(object instance)
{
}
one can write:
void Process<T>(T instance)
{
}
and thus preserve the static type information about the parameter. One can still call this method like Process(stuff) without having to explicitly specify the generic type parameter: Process<StuffType>(stuff).
Casting by example
This technique can be useful (among others) in one specific situation - where we can't specify the generic type parameter, because it is of an anonymous type. We can still call a generic method and "specify" the anonymous type parameter using type inference (more precisely, the smart C# compiler will do it for us). I blogged more about this here: http://kirillosenkov.blogspot.com/2008/01/how-to-create-generic-list-of-anonymous.html
Static data is not shared among constructed generic types
Jacob Carpenter reminds us of this important (and useful!) fact: http://jacobcarpenter.wordpress.com/2008/07/21/c-reminder-of-the-day/
You can constrain a generic type parameter on a type to be its derived type
Darren Clark mentions another nice trick with generics:
public class MyBase<T> where T: MyBase<T>
Here, we essentially limit T to be a type derived from MyBase<T>.
Limits of type inference
C# 3.0 compiler is much smarter than the 2.0 when it comes to type inference. Jon Skeet described this really well in his book C# In Depth. However there is still room for future improvement - the type inferencing engine can be made even smarter to deduce types from the generic constraints. I hit this limitation recently when I tried to implement the topological sorting algorithm as a mix-in (using extension methods and generic type inference). Paste this program and it will compile fine, however if you remove the generic arguments in the call to TopologicalSort in Main(), it will fail to compile:
using System.Collections.Generic;
// Generalized algorithms and data-structures - I want to reuse them by "mixing-in"
interface IDependencyNode<TNode, TNodeList>
where TNode : IDependencyNode<TNode, TNodeList>
where TNodeList : IDependencyList<TNode>
{
// TNodeList Dependencies { get; } <-- that's why I need TNodeList
}
interface IDependencyList<TNode> : IEnumerable<TNode> { }
static class Extensions
{
public static IEnumerable<TNode> TopologicalSort<TNode, TNodeList>(this TNodeList nodes)
where TNode : IDependencyNode<TNode, TNodeList>
where TNodeList : IDependencyList<TNode>
{
return null; // algorithm goes here
}
}
// Mixing-in to my concrete world of Figures and FigureLists
// I basically get the implementation of topological sort for free
// without inheriting from any classes
class Figure : IDependencyNode<Figure, FigureList> { }
class FigureList : List<Figure>, IDependencyList<Figure> { }
class Program
{
static void Main(string[] args)
{
FigureList list = new FigureList();
// wouldn't it be sweet if we could infer the type arguments here??
// list.TopologicalSort(); // doesn't compile
list.TopologicalSort<Figure, FigureList>(); // compiles fine
}
}
In this case, one could potentially figure out the types Figure and FigureList (at least, it is doable by a human :), but then the type inferencing algorithm becomes even more complex than it is now (in fact, I suspect that it would become as powerful as a typical Prolog solver because it would require unification). The C# compiler team has certainly higher priority tasks now than implementing Prolog into the C# compiler.
Finally, when I look at the code above, it is too complex, difficult to understand and clumsy. One shouldn't pay such a high price for the flexibility of mix-ins. There is a much more simple and elegant solution to the problem which I hope to come back to later.