Monday, April 8, 2013

Auto-Generating generic Tuple classes with T4 in C#

And yet again, here's another perfect example for using the T4 code generation toolkit in Visual Studio.

For a recent project, I wanted to use the Tuple class provided in the .NET Framework...problem was, I was stuck with .NET 3.5 and the Tuple class was introduced in version 4 of the framework. So, instead of writing a single Tuple class by hand (which would have been very trivial to do), I decided to write a T4 template which would generate all the Tuple classes I needed automatically.

The template itself is very trivial and the number of Tuple classes it generates is dependent on the value that I specify in the max variable at the top of the template.

For each of the Tuple classes the template generates, it creates a contructor which accepts all of the properties that the tuple will contain and also the actual properties that will be accessed from the outside.

Here's an example of what the template autogenerates for me when using max = 3 (granted, the first class it generates is a bit pointless but that can be easily omitted from within the template if it bothers you):
namespace FunWithTuples
{
    /// <summary>
    /// Represents a 1-tuple
    /// </summary>
    public class Tuple<T1>
    {
        public T1 Item1 { get; set; }

        public Tuple(T1 item1) 
        {
            Item1 = item1;
        }
    }

    /// <summary>
    /// Represents a 2-tuple
    /// </summary>
    public class Tuple<T1, T2>
    {
        public T1 Item1 { get; set; }
        public T2 Item2 { get; set; }

        public Tuple(T1 item1, T2 item2) 
        {
            Item1 = item1;
            Item2 = item2;
        }
    }

    /// <summary>
    /// Represents a 3-tuple
    /// </summary>
    public class Tuple<T1, T2, T3>
    {
        public T1 Item1 { get; set; }
        public T2 Item2 { get; set; }
        public T3 Item3 { get; set; }

        public Tuple(T1 item1, T2 item2, T3 item3) 
        {
            Item1 = item1;
            Item2 = item2;
            Item3 = item3;
        }
    }
}



And this is the full T4 Template:

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>

<#
var max = 3; // The total number of classes to generate
#>
namespace FunWithTuples
{
<#
max += 1;
for (var i = 1; i < max; ++i) {
#>
    /// 
    /// Represents a <#=i#>-tuple
    /// 
    public class Tuple<#=GetGenericTypesSignature(i)#>
    {
<#
for (var j = 1; j < i + 1; ++j) {
#>
        public T<#=j#> Item<#=j#> { get; set; }
<#
}
#>

        public Tuple(<#=GetConstructorArguments(i)#>) 
        {
<#
for (var j = 1; j < i + 1; ++j) {
#>
            Item<#=j#> = item<#=j#>;
<#
}
#>
        }
    }

<#
}
#>
}
<#+
    public string GetGenericTypesSignature(int total) {
        return String.Format("<{0}>", String.Join(", ", Enumerable.Range(1, total).Select(n => "T" + n).ToArray()));
    }

    public string GetConstructorArguments(int total) {
        return String.Join(", ", Enumerable.Range(1, total).Select(n => String.Format("T{0} item{0}", n)).ToArray());
    }
#>