Приветствую, Хабр!
Сегодня мы поговорим о рутине. Время от времени каждому программисту приходится совершать много нудной, объемной и шаблонной работы, которую постоянно так и хочется автоматизировать, да руки не доходят. Вот об одном малоизвестном способе упростить себе жизнь с помощью кодогенерации я и хочу сегодня рассказать сообществу дотнетчиков. Способ известен как Text Template Transformation Toolkit или попросту T4.
Представьте себе следующую ситуацию: вам необходимо описать некий конечный автомат. Реализован он будет невероятно криво, но пример для иллюстрации вполне подходит. В ходе реализации сердцем автомата стала функция, которая принимает один параметр — текущее состояние, и в зависимости от его значения выполняет те или иные действия. Все возможные состояния автомата вы заблаговременно описали в enum`е, и теперь грустно смотрите на монитор: предстоит описывать гигантский switch на полэкрана, а как же не хочется…
Конкретизируя задачу, пусть в автомате 3 состояния (а вы представьте ситуацию, когда их 43 — скажем, это какое-нибудь сообщение WinAPI), и enum выглядит так:
Задача — создать с помощью T4 шаблон, который будет генерировать по нему следующий код:
Здесь и далее я неявно предполагаю, что у вас установлена Visual Studio 2005/2008/2010. В произвольный проект добавляем новый файл с расширением *.tt, к примеру Switch.tt. Это расширение — стандартное для файлов шаблонов и автоматически распознается Студией. Заметьте, в Solution Explorer к нему автоматически добавился ещё один узел, содержащий пока что пустой файл Switch.cs. В нём потом окажется результат генерации.
Итак, сначала — рецепт, потом объяснения. В пустой файл Switch.tt вставим следующий текст:
После нажатия Ctrl+S стоит всего лишь посмотреть в Switch.cs и мы увидим там необходимый текст.
Взгляните пристальнее на текст шаблона. T4 использует декларативный стиль, поэтому повсеместно в его коде используются теги в стиле ASP.NET. Итак, весь код, который находится в файле шаблона, можно разделить на пять видов:
Чтобы в полной мере осознать принципы, по которым пишутся шаблоны, придётся заглянуть за кулисы: узнать, как именно T4 интерпретирует текст нашего .tt-файла. Для этого найдите в своей папке %TEMP% последний созданный .cs-файл. Если выкинуть многочисленные #line и отформатировать код, то в данном случае он будет выглядеть приблизительно так:
Как видим, T4 автоматически создаёт класс, наследник
Генератор написан на C#. Почему? Потому что мы так приказали. Директива
Ещё, к слову, если бы в тексте шаблона не было указано
Надо признать, что, реализовывая разбор шаблона и создание генератора, ребята из Microsoft использовали свои же реализованные возможности крайне неэкономно. К примеру, возьмём идентацию кода. Генератор, приведённый выше, читать проблематично — глаза лопаются от всех этих бесконечных выводимых "\t\t\t\t".
А ведь всего-то нужно было заюзать простую функцию
А ещё есть свойство
Аналогичная ситуация с переносами строк — вместо того чтобы плодить в строковых литералах "\r\n", можно было просто заменить Write на WriteLine. В рамках текущей платформы, разумеется.
И не мешало бы упомянуть методы
По предыдущей задаче может сложиться впечатление, что шаблоны T4 выгодно использовать только для мелких подручных задач, чтобы не писать много нудного C# кода. Ничего подобного. Яркий иллюстрирующий это пример: шаблон из коллекции Tangible, который по указанной вами папке с изображениями создаёт простую html-страницу с галереей картинок.
Что самое интересное, он недлинный, легко читается и модифицируется под нужды разработчика.
Чтобы не уродовать статью лишними блоками текста, я разместил его на pastebin.
А в заключение статьи — несколько полезных ссылок.
Ссылка на документацию MSDN по T4 была приведена в начале статьи. Это — наиболее полное описание синтаксиса и возможностей генератора, тем не менее, без каких-либо полезных на практике примеров.
Блог Oleg Sych — кладезь полезной информации по T4. Здесь вы сможете узнать в подробностях о том, как отлаживать и модифицировать шаблоны, найдёте множество готовых примеров (хранимые процедуры, классы LINQ и Entity Framework, конфиги, скрипты MSBuild и WiX и т.д.), а также рассказ о более «продвинутых» возможностях шаблонов, которые не поместились в данную статью. Возможно, я адаптирую эти сведения к хабрааудитории как-нибудь позже, в следующей статье.
T4 Toolbox — набор готовых темплейтов для создания «Нового файла» T4 в Visual Studio.
T4 Editor от Tangible Engineering — удобный редактор с подсветкой и IntelliSense, внедряющийся как плагин к Visual Studio. Доступен в двух редакциях: платной и урезанной бесплатной. Лично мне возможностей бесплатной версии хватает за глаза.
Чем ещё удобен Tangible — своей неплохой галереей готовых шаблонов.
T4 Editor от Clarius Consulting — ещё один редактор для шаблонов T4 в виде плагина к Студии. От предыдущего отличается лишь тем, что функционал его бесплатной редакции немного сильнее урезан.
Мораль сей басни такова: если вас судьба поставила перед неизбежностью монотонной и нудной работы, никогда не стоит отказываться от помощи. Особенно — от помощи компьютера.
Удачной вам кодогенерации! :)
Сегодня мы поговорим о рутине. Время от времени каждому программисту приходится совершать много нудной, объемной и шаблонной работы, которую постоянно так и хочется автоматизировать, да руки не доходят. Вот об одном малоизвестном способе упростить себе жизнь с помощью кодогенерации я и хочу сегодня рассказать сообществу дотнетчиков. Способ известен как Text Template Transformation Toolkit или попросту T4.
Знакомство с Т4
Пример задачи
Представьте себе следующую ситуацию: вам необходимо описать некий конечный автомат. Реализован он будет невероятно криво, но пример для иллюстрации вполне подходит. В ходе реализации сердцем автомата стала функция, которая принимает один параметр — текущее состояние, и в зависимости от его значения выполняет те или иные действия. Все возможные состояния автомата вы заблаговременно описали в enum`е, и теперь грустно смотрите на монитор: предстоит описывать гигантский switch на полэкрана, а как же не хочется…
Конкретизируя задачу, пусть в автомате 3 состояния (а вы представьте ситуацию, когда их 43 — скажем, это какое-нибудь сообщение WinAPI), и enum выглядит так:
enum State
{
Alive,
Dead,
Schrodinger
}
Задача — создать с помощью T4 шаблон, который будет генерировать по нему следующий код:
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;
}
}
Рецепт решения
Здесь и далее я неявно предполагаю, что у вас установлена Visual Studio 2005/2008/2010. В произвольный проект добавляем новый файл с расширением *.tt, к примеру Switch.tt. Это расширение — стандартное для файлов шаблонов и автоматически распознается Студией. Заметьте, в Solution Explorer к нему автоматически добавился ещё один узел, содержащий пока что пустой файл Switch.cs. В нём потом окажется результат генерации.
Итак, сначала — рецепт, потом объяснения. В пустой файл Switch.tt вставим следующий текст:
<#@ 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
}
#>
После нажатия Ctrl+S стоит всего лишь посмотреть в Switch.cs и мы увидим там необходимый текст.
Как такое волшебство получается?
Взгляните пристальнее на текст шаблона. T4 использует декларативный стиль, поэтому повсеместно в его коде используются теги в стиле ASP.NET. Итак, весь код, который находится в файле шаблона, можно разделить на пять видов:
- Текстовый блок — не обрамлён никакими тегами, копируется в выходной файл «как есть».
- Директивы — устанавливают настройки шаблона, используются T4 в процессе генерации. Обрамляются тегами
<#@ #>
. Ниже я опишу самые используемые из них. - Блок кода — этот код после дословно будет вставлен T4 в класс, который собственно и занимается генерацией выходного файла. Обрамляется тегами
<# #>
. - Блок выражения — внедряется внутрь текстового блока. Он содержит некоторое выражение, которое возможно скомпилировать в рамках класса генерации, к примеру, переменную, ранее определённую в каком-нибудь блоке кода. При генерации выходного файла вместо него подставится текущее значение этого выражения. Обрамляется тегами
<#= #>
. - Блок классового свойства — обрамляется тегами
<#+ #>
. Его предназначение мы обсудим чуть позже.
Чтобы в полной мере осознать принципы, по которым пишутся шаблоны, придётся заглянуть за кулисы: узнать, как именно T4 интерпретирует текст нашего .tt-файла. Для этого найдите в своей папке %TEMP% последний созданный .cs-файл. Если выкинуть многочисленные #line и отформатировать код, то в данном случае он будет выглядеть приблизительно так:
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\tswitch (state)\r\n\t{\r\n");
foreach (string Value in Enum.GetNames(typeof(State))) {
this.Write("\t\tcase State.");
this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(Value));
this.Write(":\r\n\t\t\t// code here\r\n\t\t\tbreak;\r\n");
}
this.Write("\t\tdefault:\r\n\t\t\t// code here\r\n\t\t\tbreak;\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
}
}
}
Как видим, T4 автоматически создаёт класс, наследник
Microsoft.VisualStudio.TextTemplating.TextTransformation
, и переопределяет в нём один-единственный метод TransformText()
, который и формирует по частям текст выходного файла. Блоки кода из исходного шаблона становятся частью кода метода, а текстовые блоки добавляются в выходной текст посредством вызовов this.Write
. Аналогичным образом интерпретируются блоки выражения. Что же касается блоков классовых свойств, то, как теперь видно, они представляют собою ту информацию, которая будет добавлена потом внутрь класса-генератора, чтобы в коде метода TransformText()
можно было на неё ссылаться. В нашем примере это определение enum`а State, но вы прекрасно можете описать внутри <#+ #>
, к примеру, функцию, которую будет активно использовать генератор.Генератор написан на C#. Почему? Потому что мы так приказали. Директива
<#@ template language="C#v3.5" #>
задаёт язык программирования, который используется в блоках кода, классовых свойств и выражений. На данный момент у неё всего четыре возможных значения: "C#"
, "VB"
, "C#v3.5"
и "VBv3.5"
Ещё, к слову, если бы в тексте шаблона не было указано
<#@ template debug="True" #>
, то никаких следов генератора, равно как и связанной с ним дебаг-информации, вы бы в %TEMP% так и не нашли.Директивы
<#@ assembly name="System.Data" #>
прилинкует к проекту генератора сборку System.Data. Работает аналогично «Add Reference» в Visual Studio.<#@ import namespace="System.Diagnostics" #>
добавит в генератор ссылку-using на пространство имён System.Diagnostics.<#@ include file="Another.tt" #>
вставит в данную точку содержимое шаблона Another.tt.<#@ output extension=".cs" encoding="UTF-8"#>
скажет T4, что выходной файл должен быть в кодировке UTF-8 и иметь расширение .cs.
Кое-что особенное
TextTransformation
Надо признать, что, реализовывая разбор шаблона и создание генератора, ребята из Microsoft использовали свои же реализованные возможности крайне неэкономно. К примеру, возьмём идентацию кода. Генератор, приведённый выше, читать проблематично — глаза лопаются от всех этих бесконечных выводимых "\t\t\t\t".
А ведь всего-то нужно было заюзать простую функцию
PushIndent("\t")
. Она дописывает новый кусочек к общему префиксу, который потом будет добавляться к каждому Write`у. Как нетрудно догадаться, парная ей функция PopIndent()
убирает из префикса последний добавленный туда кусочек.А ещё есть свойство
CurrentIndent
и метод ClearIndent()
. Просто информации ради :)Аналогичная ситуация с переносами строк — вместо того чтобы плодить в строковых литералах "\r\n", можно было просто заменить Write на WriteLine. В рамках текущей платформы, разумеется.
И не мешало бы упомянуть методы
Warning()
и Error()
. Будучи вызванными в коде генератора, они вызовут соответственно предупреждение либо ошибку, которые отобразятся в Error List при попытке интерпретации шаблона. Очень удобно использовать в непредвиденных ситуациях в блоках кода.Профессиональные примеры
По предыдущей задаче может сложиться впечатление, что шаблоны T4 выгодно использовать только для мелких подручных задач, чтобы не писать много нудного C# кода. Ничего подобного. Яркий иллюстрирующий это пример: шаблон из коллекции Tangible, который по указанной вами папке с изображениями создаёт простую html-страницу с галереей картинок.
Что самое интересное, он недлинный, легко читается и модифицируется под нужды разработчика.
Чтобы не уродовать статью лишними блоками текста, я разместил его на pastebin.
T4-штучки
А в заключение статьи — несколько полезных ссылок.
Ссылка на документацию MSDN по T4 была приведена в начале статьи. Это — наиболее полное описание синтаксиса и возможностей генератора, тем не менее, без каких-либо полезных на практике примеров.
Блог Oleg Sych — кладезь полезной информации по T4. Здесь вы сможете узнать в подробностях о том, как отлаживать и модифицировать шаблоны, найдёте множество готовых примеров (хранимые процедуры, классы LINQ и Entity Framework, конфиги, скрипты MSBuild и WiX и т.д.), а также рассказ о более «продвинутых» возможностях шаблонов, которые не поместились в данную статью. Возможно, я адаптирую эти сведения к хабрааудитории как-нибудь позже, в следующей статье.
T4 Toolbox — набор готовых темплейтов для создания «Нового файла» T4 в Visual Studio.
T4 Editor от Tangible Engineering — удобный редактор с подсветкой и IntelliSense, внедряющийся как плагин к Visual Studio. Доступен в двух редакциях: платной и урезанной бесплатной. Лично мне возможностей бесплатной версии хватает за глаза.
Чем ещё удобен Tangible — своей неплохой галереей готовых шаблонов.
T4 Editor от Clarius Consulting — ещё один редактор для шаблонов T4 в виде плагина к Студии. От предыдущего отличается лишь тем, что функционал его бесплатной редакции немного сильнее урезан.
Заключение
Мораль сей басни такова: если вас судьба поставила перед неизбежностью монотонной и нудной работы, никогда не стоит отказываться от помощи. Особенно — от помощи компьютера.
Удачной вам кодогенерации! :)