Compile Time Generics in C# and VB... sort of

I've had a major gripe with .NET for a while; a feature that I was addicted to in C++, but is just not feasible in (or implemented, I don't know which) in .NET: Compile Time Generics.

I seem to constantly run into scenarios where I want to templatize a class, but it is not possible for one reason or another. For example, consider this template struct that would work perfectly fine in C++:

namespace TaskTest
{
    unsafe struct Vector<MyVectorType, MyVectorSize>
    {
        public fixed MyVectorType Items[MyVectorSize];

        public static Vector operator+(Vector one, Vector two)
        {
            for (int i = 0; i < MyVectorSize; i++)
            {
                one.Items[i] += two.Items[i];
            }
            return one;
        }
    }
}

This is a very useful struct when doing any low level graphics related development (OpenGL, Direct3D), but it is not available on .NET Framework. (Well, .NET 3.5 on full blown Windows has a Vector3D)

Incidentally, the fixed size array means it's contents are actually a part of the total memory footprint of the structure. I.e., if you were to get a pointer to that structure, the first 4 bytes would be the first element in the array. If it were a regular array, the first 4 bytes would be a pointer to that array object. This is useful because you can pass an array of managed Vectors to OpenGL and all the data is in one contiguous block of memory.

Anyhow, I digress; I needed to templatize this structure into several flavors: Vector4f (4 floats), Vector3f, Vector4i (4 ints), Vector3i, Vector4d... etc, etc. Then imagine doing this again for Matrix3f, Matrix4f... etc.

But this is not possible with .NET generics for a couple reasons:

  1. You can't pass in an int value as a template argument.
  2. You can't constrain the template type MyVectorType to require an operator+ be implemented. You get a compile time error on the line of code that attempts to add the types.

Or consider this classic example:

namespace TaskTest
{
    public partial class FunkyControl<MyControlType> : MyControlType
    {
        bool myHasGottenFocus = false;
        protected override void OnGotFocus(EventArgs e)
        {
            if (myHasGottenFocus)
                return;
            myHasGottenFocus = true;
            MessageBox.Show("Funky!");
        }
    }
}

This class takes any System.Windows.Forms.Control inheriting type and overrides it so when the user first puts focus on it, it pops up a MessageBox. Or it may not even be a System.Windows.Control, it may just be an object that has a virtual OnGotFocus method. However this scenario is not possible either: the generic parameter can not be the base class of the resultant type.

So, my awesome hack of a solution was to create an MSBuild Task that basically looks at a code file and does a copy, paste, search, and replace to generate new code files with your resultant types:

using System;
using System.Collections.Generic;
using System.Text;

// @CompileTimeGeneric Vector4f::MyVectorSize:4;<MyVectorType>:;MyVectorType:float;Vector:Vector4f@
// @CompileTimeGeneric Vector3f::MyVectorSize:3;<MyVectorType>:;MyVectorType:float;Vector:Vector3f@

namespace TaskTest
{
    unsafe struct Vector<MyVectorType>
    {
        public fixed MyVectorType Items[MyVectorSize];

        public static Vector operator+(Vector one, Vector two)
        {
            for (int i = 0; i < MyVectorSize; i++)
            {
                one.Items[i] += two.Items[i];
            }
            return one;
        }
    }
}

Note the comments. The build task locate the files you wish to create from the contents of the input file by searching for a certain regular expression match.

A sample match would be the following:

@CompileTimeGeneric OutputFileWithoutExtension1:Search1:Replace1;Search2:Replace2@

@CompileTimeGeneric OutputFileWithoutExtension2:Search1:Replace1@

The search and replace takes place in the order listed. One input file can generate as many output files as you need. Unfortunately this isn't a full blown implementation of generics like C++: it doesn't support recursive instantiation of generic types (like using the compiler to calculate out the Nth value in the Fibonacci series). But it works well enough for what I need!

Here is the full source for the Tasks.dll and Tasks.Targets.xml. You will need to add the Tasks.Targets.xml file to your project.

<Import Project="..\Tasks.Targets.xml" />

And for all files that you want to generate Compile Time Generics, change the Build action to "CompileTimeGeneric" in the properties window:

CTGThumbnail

That should do it! The task will create temporary files containing the generics in the bin\obj directory and add them to your Compile list.

P.S. Yes, I know this is a complete hack.

P.P.S. No, I am not proud of this code by any means. It's a means to an end; a way to save time without sacrificing performance so I can do development on more interesting stuff.

P.P.P.S. Mmm, template metaprogramming. I miss you.

3 comments:

Jared Parsons said...

Using msbuild to essentially implement compile time operator resolution is ... evil :). But hey it gets the job done.

The most elegant way would be for an interface based approach (IOperator<T>) but that's may not be performant enough for this scenario.

Koush said...

Yeah, the vector and matrix math that I'll be using these classes for will happen hundreds, if not thousands, of times per frame. It has to be fairly performant.

Anonymous said...

This is exactly what I was looking for :)

I really miss the power of C++ templates, C# has some amazing great features, but so many aspects of the STL library just blow C# out of the water for creating 'generic' code... the boost library shows off the power of what templates provide that can compiletime solve for things that take way to long runtime.

Thanks Again!
- Phil