More comments on generics

More comments on generics

Rate This
  • Comments 9

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.

kick it on DotNetKicks.com
  • Firstly, thanks for the book plug - it's always nice to get a mention :)

    It sounds like we've been running into similar (but not the same) restrictions in .NET/C# around generics. I blogged my findings when porting Protocol Buffers recently - any comments would be welcome.

    http://msmvps.com/blogs/jon_skeet/archive/tags/Protocol+Buffers/default.aspx

    Jon

  • class Blah : IDependencyNode<Figure, OtherList>, IDependencyNode<Blah, AnotherList> { }

    class AnotherList : List<Figure>, IDependencyList<Figure>, IDependencyList<Blah> { }

    So yeah, I was just thinking about how painful it would be to have to figure out that algorithm.

    May I suggest to you..?

    using System.Collections.Generic;

    interface IDependencyNode<TNode>

       where TNode : IDependencyNode<TNode>

    {

       // IEnumerable<TNode> Dependencies { get; }

    }

    static class Extensions

    {

       public static IEnumerable<TNode> TopologicalSort<TNode>(this IEnumerable<TNode> nodes)

           where TNode : IDependencyNode<TNode>

       {

           return null; // algorithm goes here

       }

    }

    class Figure : IDependencyNode<Figure> { }

    class Program

    {

       static void Main(string[] args)

       {

           List<Figure> list = new List<Figure>();

           list.TopologicalSort(); // compiles fine

       }

    }

  • Ha-ha, Brennan, you almost make me feel like I'm an architectural astronaut :)

    This chunk of code was to demonstrate a limitation of the C# 3.0 type inference engine, and thankfully doesn't come from production code. Also the last paragraph of my post is there on purpose too.

    Jon: great blog posts! I've forwarded a link to Mads Torgersen.

    Thanks!

  • Some good tips on using Genetics http://blogs.msdn.com/kirillosenkov/archive/2008/09/15/more-comments

  • You've been kicked (a good thing) - Trackback from DotNetKicks.com

  • Your technique of Process<T>(T instance) plays nicely with extension methods:

    E.g. you can write  

    T Process<T>(this T instance) where T ....

    {

     // do stuff...

     return instance;

    }

    to get a method which you can call on any T and which will return a strongly typed T.  

    This can come in handy in fluent APIs, when you want a method that can act on a range of classes in the API, but for which the implementation is the same and the only difference is the strong return type.

  • Nice article, thanks.

    Just one more question, there is anyway to define the base type of a generic in a method?

    Something like

    public void Update<T>() where T:DataSet {}

  • Aridane: yes, it is called Generic Constraints (http://msdn.microsoft.com/en-us/library/d5x73970(VS.80).aspx) and can be used exactly as you typed.

  • Amazing Article. Thanks for such a valuable info..

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 2 and 6 and type the answer here:
  • Post