Wednesday, April 6, 2011

(Not) Understanding why template specialization isn't available in C#

As usual, I've decided to use the blogosphere to crystallize my thoughts on how templates in C++ and C# differ. Specifically, why is there no such thing as template specialization in C#?

In C++, templates are instantiated fully at compile time. If no type uses a given template, it doesn't get instantiated.

For example,

template <typename T> T max( T a, T b ) {
  if ( a >= b )
    return a;
  return b;
}

Then in some client code,

int main(...)
{
  int maxarg = max(atoi(argv[1]), atoi(argv[2]));
}

At this exact point, the C++ compiler says, ah, here's a use of max where T=int, so I'll create a specialized int max( int a, int b ), and compile that.

In C#, the paradigm is slightly different. First of all they're called generics, not templates. Second, the above code (minus the C++-isms), will not work. Namely, the definition for max will fail to compile.

Compile, you say? Why is it compiling the templatized function? It doesn't compile until it's used, right? Wrong.

C# templates are compiled to be available for all types that match a particular constraint. For example, the function max, in C#, would look like:


class Math {
  static T Max<T>( T a, T b ) where T : IComparable<T> {
    if ( a.CompareTo(b) >= 0 ) {
      return a;
    return b;
  }
}

Now, this is compiled into a dll or exe that exposes a definition of Max which allows IComparables to be pulled in. The only thing that makes this different from the following is that in the generic method, both a and b must be the same type.

class Math {
  static IComparable Max( IComparable a, IComparable b ) {
    if ( a.CompareTo(b) >= 0 ) {
       return a;
    return b;
  }
}

So, in this sense, the Max function below is more general than the ComputeMax function, since the ComputeMax function requires a and b to be the same type.

public static IComparable Max( IComparable a, IComparable b )
{
  return a.CompareTo(b) >= 0 ? a : b;
}
public static T ComputeMax<T>( T a, T b ) where T : IComparable
{
  return (T)Max(a, b);
}

Now what is the difference between IComparable and IComparable<T>? It's the same as what we discovered earlier - the templatized form ensures that the comparison is always made to an object of the same type. Observing the definitions of IComparable and IComparable<T>...

namespace System {
    public interface IComparable
    {
        int CompareTo(T other);
    }
    public interface IComparable
    {
        // Exceptions:
        //   System.ArgumentException:
        //     obj is not the same type as this instance.
        int CompareTo(object obj);
    }
}

Note that IComparable.CompareTo asks that (but does not enforce that) CompareTo throws an exception if the passed in object is not the same type as this. And there we have it - that's the advantage of IComparable<T>. It ensures that, at compilation time, the types of the two items are the same.


Now with this knowledge, we are finally ready to answer why there is no template specialization in C#. And the answer is.... I still don't know. I think actually, that it was simply a design choice. All of the fluff on StackOverflow makes it seem like Microsoft knowingly decided that it wouldn't fit into the paradigm, but my impression is that it was too much work and they figured it wouldn't be very worthwhile.

No comments:

Post a Comment