Creating Mixins with T4 in Visual Studio

One of the (few) features I really miss from C++ is the ability to use multiple inheritance to create mixins, particularly with the Curiously Recurring Template Pattern.

Scott Hanselman blogged about T4 (Text Template Transformation Toolkit) a few months back, but only recently it hit me that T4 could be combined with partial classes to create a poor man’s mixin system in C# 3.0.

For a simple example, let’s consider a class that supports IEquatable<T>. The logic that’s specific to computing equality for this class will be in the Equals and GetHashCode methods:

public class TestObject : IEquatable<TestObject>
{
    public TestObject(int value)
    {
        Value = value;
    }

    public bool Equals(TestObject other)
    {
        return other != null && Value == other.Value;
    }

    public override int GetHashCode()
    {
        return Value;
    }

    public int Value { get; set; }
}

To round out this class, we really should implement Equals(object) and overload the equality operators. This is exactly where a mixin would be useful, because that code is exactly the same in every implementation of an equatable object.

Firstly, prepare TestObject to support mixins by making it a partial class:

public partial class TestObject : IEquatable

Secondly, create the mixin source: the templated methods of the equatable implementations. Add a new item to the project, and set the name to “EquatableClass.tt”. (This implementation will be for classes only; a separate mixin would need to be created for structs since they can’t ever be null.)

Open the Properties window for this new file, and clear the Custom Tool property. This prevents an output file being generated for this template.

Fill in the EquatableClass.tt file with this code, a templated version of standard equality methods.

namespace <#= NamespaceName #>
{
    partial class <#= ClassName #>
    {
        public override bool Equals(object other)
        {
            return Equals(other as <#= ClassName #>);
        }

        public static bool operator==(<#= ClassName #> left, <#= ClassName #> right)
        {
            if (ReferenceEquals(left, right))
                return true;
            else if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
                return false;
            else
                return left.Equals(right);
        }

        public static bool operator!=(<#= ClassName #> left, <#= ClassName #> right)
        {
            if (ReferenceEquals(left, right))
                return false;
            else if (ReferenceEquals(left, null) || ReferenceEquals(right, null))
                return true;
            else
                return !left.Equals(right);
        }
    }
}

<#+
    public void SetClassName(string namespaceName, string className)
    {
        NamespaceName = namespaceName;
        ClassName = className;
    }
    string NamespaceName;
    string ClassName;
#>

This template (when included in another template) outputs the standard equality methods and also provides a way for the including template to customize the output.

Lastly, write the including template. Add another file to the project, this time named “TestObjectEquatable.tt”. Paste the following code into that file:

<#@template language="C#"#>
<#@output extension="g.cs" #>
<# SetClassName("Mixins", "TestObject"); #>
<#@include file="EquatableClass.tt"#>

These four lines have the following effects:

  1. Instructs the T4 engine that code in this template is written in C#.
  2. Sets the extension of the file to “.g.cs”; this clearly identifies the output as generated code.
  3. Calls the SetClassName method defined in the first template so that the right namespace and class name are used in the generated code.
  4. Includes the first template, causing all the code it defines to be generated.

When the project is built, the TestObjectEquatable.tt template will be processed to generate a partial class containing the supporting equality methods. The C# compiler will compile this with the primary partial class, giving a complete implementation of the standard equality methods.

Now that the template is defined, only the four lines in the second template need to be copied to add equatable methods to a new class, instead of the 25 lines of boilerplate code that the template generates.

One significant drawback with this approach is that a template is only reprocessed when it changes; the template engine isn’t aware that TestObjectEquatable.tt depends on EquatableClass.tt, and that it should be reprocessed if the latter changes. For this reason, it’s probably best to keep the included template rather simple; it would be better to have the generated methods delegate to composed objects or static methods on utility classes rather than including a lot of complicated logic in the template itself.

To learn more about T4, I highly recommend Oleg Sych’s blog, which contains many tutorials, examples, and information about his T4 Toolbox project.

Posted by Bradley Grainger on April 08, 2009