|
T4 is a Text Template engine that comes with Visual Studio 2008. I'm a little fuzzy on its origins and true purpose, but from the documentation that is available on MSDN, it looks as though T4 was originally conceived as the underlying plumbing that allowed DSLs (Domain-Specific Languages) to programmatically generate compileable classes on the fly.
The benefit of this engine is that it's available for code generation even if you're not working on a DSL. In fact I would go so far as to say that regardless of it's original intent, T4 probably has wider appeal among developers looking to code generate as a solution to the "boilerplate of spaghetti" problem than it does among the relative minority of people building DSLs. I'm not saying that it's less meaningful to DSL builders, I'm just saying that there are more devs who need code generation for their day-to-day tasks than there are devs who are building DSLs.
Before diving into a sample I just want to try and make it clear that T4 is a code eneration utility. It handles those situations where you cannot fix your redundant or tedious code problems with inheritance, DI, or Aspects.
One of the tasks that I do all of the time when building an application with a desktop (or Silverlight) GUI is build classes that implement INotifyPropertyChanged. This allows me to bind the GUI to those classes and have the GUI react when those properties change. The problem is that for every single property that I want the class to have, I have to manually type in the code that fires the NotifyPropertyChanged event. While I can factor out the 3 lines of code for the event firing into a base class, I'm still left with this huge stream of property definitions. The properties all have the same shape (this should be a good clue that we can use codegen here) and all behave the same way. The getter returns the backing variable and the setter sets the backing variable and invokes a method which fires the property changed notification.
So in this T4 introduction I'm going to build a T4 template that automatically generates these notifier properties on a ViewModel class for me. It's going to be a partial class so if I want to add custom methods to the already generated members, I can do that without worry of having the generation process wipe out my code.
First thing to do is simply create a new file with the ".tt" extension in your project. In my case, I'm going to create a template called StateNotifierClass.tt. Just add a new file to the project with the ".tt" extension. Set the code for this template to the following:
<#@ template language="C#" #>
<#@ output extension="cs" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.ComponentModel;
namespace <#= this.Namespace #>
{
public partial class <#= this.ClassName #> : INotifyPropertyChanged
{
<# for (int idx = 0; idx < this.properties.GetLength(0); idx++)
{
#>
private <#= this.properties[idx,0] #> _<#= this.properties[idx,1].ToLower() #>;
<#
}
#>
public void DoHello()
{
Console.WriteLine("foo");
}
<# for (int idx = 0; idx < this.properties.GetLength(0); idx++)
{
#>
public <#= this.properties[idx,0] #> <#= this.properties[idx,1] #>
{
get
{
return _<#= this.properties[idx,1].ToLower() #>;
}
set
{
_<#= this.properties[idx,1].ToLower() #> = value;
NotifyChange("<#= this.properties[idx,1]#>");
}
}
<#
}
#>
protected void NotifyChange(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
<#+
string Namespace = "Demo";
string ClassName = "DemoClass";
string[,] properties = {
{"int", "Property1"},
{"string", "Property2"}};
#>
With this template in place, you should see that your solution explorer has a new file beneath your template called StateNotifierClass.cs. Take a look at it, you'll see that it has a namespace, a classname, private member variables providing a backing store for the properties and public property definitions that are not only strongly typed without having to use generics but they also fire the property changed event notification.
This is great, but now what does it look like to build a real, live view model class using this template? Here is a view model class template for a stock quote that re-uses the previous template. As you can see, looking at this template is a LOT easier than looking at the huge pile of generated properties and you know immediately what simple, notification-enabled state members are exposed by that class:
<#
this.Namespace = "DotNetAddict.Samples";
this.ClassName = "StockQuote";
this.properties = new string[,]{
{"string", "Symbol"},
{"decimal", "Price"},
{"DateTime", "TimeStamp"},
{"int", "Quantity"}};
#>
<#@ include file="StateNotifierClass.tt" #>
That's it! Obviously there is a lot more to T4 than what I'm showing here and if I end up spending more time using it I will do some more blog posts about it. The bottom line here is that for lightweight code generation tasks, you no longer need to look outside Visual Studio for third party codegen products... It's all right there for you.
Obviously your mileage may vary, but I know from experience that having this tool available on some of my previous projects would've saved me DAYS of work and the end result would've been cleaner, more elegant, and a lot easier to change.
Just to add to this...another awesome side-effect of doing it this way is
that you will never again fall victim to the typo problem where you spell
the name of your property wrong in the setter and the notification never
fires the way you want it to. Back in the good old days I remember a lot of
people were skeptical of code generation. I say that, used tastefully and
tactically, codegen is your friend.
In fact, there is a solution of exactly the same problem using AOP: The
PostSharp documentation (http://www.postsharp.org/) contains a code sample
for using the CompoundAspect that does A) decorate a class with
INotifyPropertyChanged, and B) decorate all publicly visible property
setters with some notification code.