Showing posts with label T4. Show all posts
Showing posts with label T4. Show all posts

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());
    }
#>


Wednesday, December 19, 2012

Auto-Generating EventArgs classes with T4

T4 is a code generator built into Visual Studio. In this post, I will present a T4 template file which I've used to help auto-generate Event Arguments classes in C#.

Normally, event arguments classes always follow the same structure. You typically have a set of public properties in a class which extends EventArgs and a constructor which takes in the same number of formal arguments.

Here's a typical event arguments class:

public class OperationCompletedEventArgs : EventArgs
{
    public bool Result { get; private set; }

    public OperationCompletedEventArgs(bool result)
    {
        Result = result;
    }
}

Given the repetitive nature of how typical event arguments classes are structured, they make a perfect fit for an example of code generation.



The intent of my template file for generating event arguments classes is to be able to define a declaration of what classes I want to be generated and then let the template generate those classes for me.

The declaration part of the template looks something like this:

var collection = new List<EventArgumentsRepresentation>
                 {
                     new EventArgumentsRepresentation("OperationCompleted")
                     {
                         Members = new Dictionary<string, string> { 
                             {"Result", "bool"}
                         }
                     },
                     new EventArgumentsRepresentation("GenericAndPartial")
                         {
                             AccessModifier = "internal",
                             IsPartial = true,
                             GenericTypes = new List<string>() { "T", "TK"},
                             Members = new Dictionary<string, string> { 
                                 {"GenericMember1", "T"},
                                 {"GenericMember2", "TK"}
                             }
                         }
                 };


Given the above structure, the template would then generate the following two classes for me:

public class OperationCompletedEventArgs : EventArgs
{
    public bool Result { get; private set; }

    public OperationCompletedEventArgs(bool result) 
    {
        Result = result;
    }
}

internal partial class GenericAndPartialEventArgs<T, TK> : EventArgs
{
    public T GenericMember1 { get; private set; }
    public TK GenericMember2 { get; private set; }

    public GenericAndPartialEventArgs(T genericMember1, TK genericMember2) 
    {
        GenericMember1 = genericMember1;
        GenericMember2 = genericMember2;
    }
}

The next I would want a new event arguments class, I would just add a new instance of EventArgumentsRepresentation to the list and let the template do the work.



And now, for the entire template file. If you want to use it for your own projects, create a new "Text Template" (.tt file) in Visual Studio and copy the below template into it. Once you then save the template file, the classes will be generated into a .cs file.

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

<#
var namespaceName = "EventArgumentsGenerationDemo";
var usings = new List<string> {
    "System",
};

var collection = new List<EventArgumentsRepresentation>
                 {
                     new EventArgumentsRepresentation("OperationCompleted")
                     {
                         Members = new Dictionary<string, string> { 
                             {"Result", "bool"},
                         },
                     },
                     new EventArgumentsRepresentation("GenericAndPartial")
                         {
                             AccessModifier = "internal",
                             IsPartial = true,
                             GenericTypes = new List<string>() { "T", "TK"},
                             Members = new Dictionary<string, string> { 
                                 {"GenericMember1", "T"},
                                 {"GenericMember2", "TK"},
                             },
                         },
                     
                 };
#>
namespace <#=namespaceName#>
{
<#
foreach (var usingDirective in usings) {
#>
    using <#=usingDirective#>;
<#
}
#>

    /*
     * This file is autogenerated.  
     *
     * If you want to edit anything from here, use the EventArgumentsClasses.tt file.
     */

<#
foreach (var eventArgsRepresentation in collection) {
#>
    <#=eventArgsRepresentation.ClassSignature#>
    {
<#
foreach (var member in eventArgsRepresentation.Members) {
#>
        public <#=member.Value#> <#=EventArgumentsRepresentation.ToPascalCase(member.Key)#> { get; private set; }
<#
}
#>

        public <#=eventArgsRepresentation.ClassName#>(<#=eventArgsRepresentation.GetConstructorArguments()#>) 
        {
<#
foreach (var member in eventArgsRepresentation.Members) {
#>
            <#=EventArgumentsRepresentation.ToPascalCase(member.Key)#> = <#=EventArgumentsRepresentation.ToCamelCase(member.Key)#>;
<#
}
#>
        }
    }

<#
}
#>
}

<#+
internal class EventArgumentsRepresentation
{
    public string ClassName { get; private set; }

    public string AccessModifier { get; set; }
    public bool IsPartial { get; set; }
    public List<string> GenericTypes {get; set;}
    public Dictionary<string, string> Members { get; set; }
    public string ClassSignature {
        get {
            return String.Format("{0} {1}class {2}{3} : EventArgs", AccessModifier, IsPartial ? "partial " : "", ClassName, GetGenericTypesSignature());
        }
    }

    public EventArgumentsRepresentation(string name)
    {
        ClassName = String.Format("{0}EventArgs", ToPascalCase(name));
        AccessModifier = "public";
        GenericTypes = new List<string>();
        Members = new Dictionary<string, string>();
    }

    public string GetGenericTypesSignature() {
        if (GenericTypes.Count < 1) {
            return "";
        }

        return String.Format("<{0}>", string.Join(", ", GenericTypes.ToArray()));
    }

    public string GetConstructorArguments() {
        if (Members == null || Members.Count < 1) {
            return "";
        }

        return String.Join(", ", Members.Select(k => String.Format("{0} {1}", k.Value, ToCamelCase(k.Key))).ToArray());
    }

    public static string ToPascalCase(string text) {
        return char.ToUpper(text[0]) + text.Substring(1);
    }

    public static string ToCamelCase(string text) {
        return char.ToLower(text[0]) + text.Substring(1);
    }
}
#>