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:
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:
SetClassName
method defined in the first template so that the right namespace and class name are used in the generated code.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