Greetings, Habr!
Today we talk about routine. Every now and then every programmer has to do a lot of tedious, voluminous and patterned work, which I always want to automate, and my hands do not reach. This is one little-known way to simplify my life with the help of
code generation, and today I want to tell the community of dotnetchikov. The method is known as
Text Template Transformation Toolkit or simply
T4 .
Meet T4
Example task
Imagine the following situation: you need to describe a certain state machine. It will be implemented incredibly crooked, but the example for illustration is quite suitable. During the implementation, the heart of the automaton is a function that takes one parameter - the current state, and depending on its value, performs certain actions. You have described all possible states of the automaton in advance in enum ʻe, and now you are sad to look at the monitor: you have to describe a giant switch on a half-screen, but what you don’t want ...
')
Specifying the task, let it be 3 states in the automaton (and you imagine a situation when there are 43 of them - let's say this is some kind of WinAPI message), and enum looks like this:
enum State
{
Alive,
Dead,
Schrodinger
}
The task is to create with T4 a template that will generate the following code from it:
void Select(State state)
{
switch (state)
{
case State.Alive:
// code here
break ;
case State.Dead:
// code here
break ;
case State.Schrodinger:
// code here
break ;
default :
// code here
break ;
}
}
Recipe solution
Hereinafter I implicitly assume that you have installed Visual Studio 2005/2008/2010. In an arbitrary project we add a new file with the extension * .tt, for example Switch.tt. This extension is standard for template files and is automatically recognized by Studio. Notice, in the Solution Explorer another node was automatically added to it, containing for now an empty Switch.cs file. It will then be the result of generation.
So, first - a recipe, then an explanation. Insert the following text into the empty Switch.tt file:
<#@ template language="C#v3.5" debug="True" #>
<#@ output extension="cs" #>
void Select(State state)
{
switch (state)
{
<# foreach (string Value in Enum.GetNames(typeof(State))) { #>
case State.<#= Value #>:
// code here
break;
<# } #>
default:
// code here
break;
}
}
<#+
enum State
{
Alive,
Dead,
Schrodinger
}
#>
After pressing Ctrl + S, you just have to look at Switch.cs and we will see the necessary text there.
How does this magic come about?
Take a closer look at the template text. T4 uses a declarative style, so ASP.NET style tags are used throughout its code. So, all the code that is in the template file can be divided into five types:
- Text block - not framed by any tags, copied to the output file "as is".
- Directives - set the template settings, used by T4 in the generation process. Framed with tags
<#@ #>
. Below I will describe the most used ones. - Block of code - this code will literally be inserted into T4 class, which actually generates the output file. Framed with tags
<# #>
. - Block expression - embedded inside a text block. It contains some expression that can be compiled as part of a generation class, for example, a variable previously defined in a block of code. When generating the output file, the current value of this expression will be substituted for it. Framed with
<#= #>
tags. - The class property block is framed with
<#+ #>
tags. We will discuss its purpose later.
In order to fully understand the principles by which templates are written, you will have to look behind the scenes: find out exactly how T4 interprets the text of our .tt-file. To do this, locate the last created .cs file in your% TEMP% folder. If you throw out numerous #line and format the code, in this case it will look something like this:
namespace Microsoft. VisualStudio . TextTemplatingFE5E01C975766D0E3C1DD071A5BFF52A {
using System ;
using Microsoft.VisualStudio.TextTemplating.VSHost ;
public class GeneratedTextTransformation : Microsoft. VisualStudio . TextTemplating . TextTransformation {
public override string TransformText ( ) {
try {
this . Write ( "void Select(State state) \r \n { \r \n \t switch (state) \r \n \t { \r \n " ) ;
foreach ( string Value in Enum . GetNames ( typeof ( State ) ) ) {
this . Write ( " \t \t case State." ) ;
this . Write ( Microsoft. VisualStudio . TextTemplating . ToStringHelper . ToStringWithCulture ( Value ) ) ;
this . Write ( ": \r \n \t \t \t // code here \r \n \t \t \t break; \r \n " ) ;
}
this . Write ( " \t \t default: \r \n \t \t \t // code here \r \n \t \t \t break; \r \n \t } \r \n } \r \n " ) ;
}
catch ( System . Exception e ) {
System. CodeDom . Compiler . CompilerError error = new System. CodeDom . Compiler . CompilerError ( ) ;
error. ErrorText = e. ToString ( ) ;
error. FileName = "C: \\ Users \\ Alex \\ Documents \\ Visual Studio 2008 \\ Projects \\ T4Article \\ T4Article \\ Switch.tt" + "" ;
this . Errors . Add ( error ) ;
}
return this . GenerationEnvironment . ToString ( ) ;
}
enum State
{
Alive,
Dead,
Schrodinger
}
}
}
As you can see, T4 automatically creates a class, a successor to
Microsoft.VisualStudio.TextTemplating.TextTransformation
, and overrides in it a single
TransformText()
method, which forms the text of the output file in parts. The code blocks from the source template become part of the method code, and text blocks are added to the output text by calling
this.Write
. The expression blocks are interpreted in the same way. As for the class properties blocks, then, as we see now, they represent the information that will be added later inside the generator class so that it can be referenced in the code of the
TransformText()
method. In our example, this is the definition of enum `State, but you can perfectly describe inside
<#+ #>
, for example, the function that the generator will actively use.
The generator is written in C #. Why? Because we ordered it. The directive
<#@ template language="C#v3.5" #>
sets the programming language used in blocks of code, class properties and expressions. At the moment, she has only four possible values:
"C#"
,
"VB"
,
"C#v3.5"
and
"VBv3.5"
Also, by the way, if
<#@ template debug="True" #>
were not specified in the text of the template, you would never find any traces of the generator, as well as the associated debugging information, in% TEMP% .
Directives
<#@ assembly name="System.Data" #>
links the System.Data assembly to the generator project. It works in the same way as “Add Reference” in Visual Studio.<#@ import namespace="System.Diagnostics" #>
will add to the generator a using link to the System.Diagnostics namespace.<#@ include file="Another.tt" #>
inserts the contents of the Another.tt template at this point.<#@ output extension=".cs" encoding="UTF-8"#>
will tell T4 that the output file must be in UTF-8 encoding and have the extension .cs.
The remaining directives and parameters are used much less frequently, so I do not consider it necessary to spend on them your screen space :) curious people can find all the necessary links to the documentation in the postscript.
Something special
TextTransformation
It must be admitted that by implementing the template parsing and generator creation, the guys from Microsoft used their realized opportunities as extremely uneconomical. For example, take the code identification. The generator mentioned above is problematic to read - the eyes burst from all these infinite output "\ t \ t \ t \ t".
But all it was necessary was to use the simple function
PushIndent("\t")
. She adds a new piece to the general prefix, which will then be added to each Write `y. As you might guess, the
PopIndent()
function paired to it removes the last bit added to it from the prefix.
And then there is the
CurrentIndent
property and the
ClearIndent()
method. Just for the sake of information :)
A similar situation with line breaks - instead of producing in string literals "\ r \ n", you could simply replace Write with WriteLine. Under the current platform, of course.
And it would not hurt to mention the methods of
Warning()
and
Error()
. Being called in the generator code, they will cause respectively a warning or an error, which will appear in the Error List when trying to interpret the template. It is very convenient to use in unexpected situations in blocks of code.
Professional examples
According to the previous task, you may get the impression that T4 templates are advantageous to use only for small improvised tasks, in order not to write a lot of tedious C # code. Nothing like this. A striking example illustrating this is: a template from the Tangible collection, which creates a simple html-page with a gallery of pictures using the specified folder with images.
What is most interesting, it is not long, easy to read and modify to the needs of the developer.
In order not to disfigure an article with unnecessary blocks of text, I placed it
on pastebin .
T4 stuff
And in conclusion of the article - some useful links.
A link to MSDN T4 documentation was provided at the beginning of the article. This is the most complete description of the syntax and possibilities of the generator, however, without any useful examples in practice.
Blog Oleg Sych - a storehouse of useful information on T4. Here you can learn in detail how to debug and modify templates, find a lot of ready-made examples (stored procedures, LINQ and Entity Framework classes, configs, MSBuild and WiX scripts, etc.), as well as a story about more advanced ones capabilities of templates that do not fit in this article. Perhaps, I will adapt this information to habraauditorii sometime later, in the next article.
T4 Toolbox - a set of ready-made templates for creating a “New File” T4 in Visual Studio.
Tangible Engineering's T4 Editor is an easy-to-use editor with backlight and IntelliSense, being introduced as a plug-in to Visual Studio. Available in two editions: paid and trimmed free. Personally, the possibilities of the free version are enough for my eyes.
What else is convenient Tangible - its a nice gallery of ready-made templates.
Clarius Consulting's T4 Editor is another editor for T4 templates as a Studio plug-in. It differs from the previous one only in that the functionality of its free edition is cut a little more.
Conclusion
The moral of this story is as follows: if fate has put you before the inevitability of monotonous and tedious work, you should never refuse help. Especially - from the help of the computer.
Good luck with code generation! :)