Технология текстовых шаблонов T4
Введение
Основные усилия программистов при реализации и сопровождении программного проекта уходят на расширение, модификацию и исправление программного кода. На первоначальную разработку программы затрачивается относительно немного времени. Поэтому разработчики стараются внедрять решения, которые удобны и эффективны для последующих модификаций и дополнений. Легкость разработки первоначальной версии приложения не является главным приоритетом.
Наглядным и удобным способом генерации кода является применение текстовых шаблонов. Техники применения шаблонов являются в большей степени декларативными способами генерации текста. Шаблон уже предоставляет макет кода в естественном для программиста виде. Неизменяемая часть кода уже заложена в шаблоне в том же самом виде, что и готовый сгенерированный код. С помощью специальных команд, инструкций, выполнения программного кода выводятся те части шаблона, которые не являются статическими, а меняются в зависимости от метаданных и настроек. Текстовые шаблоны позволяют упрощать генерацию кода. Если нужно изменить создаваемый код, то не потребуется разбираться в программе, генерирующей код, а достаточно изменить наглядный шаблон. Однако шаблоны эффективно применимы только тогда, когда повторяющийся код встречается часто во многих участках приложения, и его объем достаточно большой.
Технология T4 (Text Templating Transformation Toolkit) является инструментом генерации кода на основе шаблонов. Она включена в Visual Studio 2008 и Visual Studio 2010. А в Visual Studio 2005 она доступна при установке DSL (Domain-Specific Language) и GAT(Guidance Automation Toolkit). С помощью текстовых шаблонов можно сгенерировать все виды текстовой информации, так называемых артефактов. Это может быть программный код на C#, VB.Net, разметка HTML, запросы SQL, документация и любая другая текстовая информация. Технологию T4 можно применять как для генерации отдельных файлов, так и крупных приложений.
Создание файла шаблона
Для создания простого шаблона T4 надо выполнить следующие шаги:
- Создать проект в Visual Studio.
- Выбрать добавление нового пункта(файла) в проект.
- Для Visual Studio 2010 выбрать Text Template. Для Visual Studio 2008 и 2005 выбрать Text File и поменять расширение файла на ".tt".
- Дать название файлу (например, MyClass.tt) и добавить его в проект.
- Добавить текст шаблона в файл. Например, такой:
<#@ template debug="false" language="C#" #> <#@ output extension=".cs" #> <# string[] vars = new string [] {"A", "B", "C"}; #> class MyClass { <# foreach (string variable in vars) { #> private int <#= variable #> = 0; <# } #> }
- Сохранить файл.
После этого шага под файлом шаблона появится файл кода на C#, как на следующем рисунке.
Мы уже рассматривали этот пример во второй лекции. Если посмотрим на код сгенерированного файла MyClass.cs, то он будет таким.
class MyClass { private int A = 0; private int B = 0; private int C = 0; }
Синтаксис T4
Шаблонные файлы имеют синтаксис, похожий на страницы ASP.Net. Встроенный редактор T4 не выполняет даже простейшую подсветку синтаксиса. Также не работает IntelliSense. То есть редактор T4 в Visual Studio больше напоминает простой текстовый редактор. Однако есть продукты сторонних производителей с большими возможностями, которые в основном хоть и платные, но имеют урезанные бесплатные версии. Например, есть редактор Т4 от Tangible Engineering.
В шаблонах T4 применяются следующие виды блоков: <#@ #> - для директив, <# #> - для блоков кода, <#+ #> - для блоков вспомогательных методов, <#= #> - блоков выражений. Все, что не входит в вышеперечисленные блоки, считается блоками текста. Они не обрамляются никакими тегами. Что мы видим в блоках текста, то будет и отображено без изменений в сгенерированном коде.
Директивы являются элементами, которые указывают, как именно должен обрабатываться шаблон. Управляющими блоками считаются блоки кода, вспомогательных методов, а также блоки выражений. Именно управляющие блоки являются программным кодом, который манипулирует участками текста и осуществляет вывод в результат работы шаблона.
Блоки кода
Блоки кода вида <#...#> содержат код на C# или Visual Basic. В ходе своей работы этот код может выводить текст в файл результата шаблона.
public class MyClass { <# for (int i=0;i < 2;i++) { #> public void DoAction() { } <# } #> }Пример 4.1.
Результатом работы вышеуказанного шаблона будет следующий код:
public class MyClass { public void DoAction() { } public void DoAction() { } }
Этот сгенерированный код некорректен, так как в классе содержатся два метода с одним именем и сигнатурой. Но сейчас это не имеет большого значения, важен сам факт вывода кода.
Блоки выражений
Блоки выражений <#=...#> служат для записи результатов выражений непосредственно в текст.
public class MyClass { <# for (int i=0;i < 2;i++) { #> public void DoAction<#=i#>() { } <# } #> }Пример 4.2.
Здесь выражение <#=i#> подставляет значение i в текст шаблона. Значение i меняется циклически. Результатом работы шаблона будет следующий код.
public class MyClass { public void DoAction0() { } public void DoAction1() { } }
Разумеется, выражение может выглядеть гораздо сложнее. Альтернативой блокам выражений является применение методов Write и WriteLine в блоке кода.
public class MyClass { <# for (int i=0;i < 2;i++) { WriteLine("\tpublic void DoAction" + i + "()"); #> { } <# } #> }Пример 4.3.
Здесь символ "\t" означает табуляцию. Результат будет тем же, что и в предыдущем примере.
Блоки вспомогательных методов
Блоки вида <#+…#> позволяют добавлять вспомогательные методы (функции) в шаблон. Они могут впоследствии вызываться изнутри шаблона, где это необходимо. Рассмотрим следующий пример.
public class MyClass { <# for (int i=0;i < 2;i++) { #> public void <#= CreateFunctionName(i)#>() { } <# } #> } <#+ public string CreateFunctionName(int counter) { string result = ""; switch (counter) { case 0: result = "DoActionOne"; break; case 1: result = "DoActionTwo"; break; } return result; } #>Пример 4.4.
В цикле вызывается метод CreateFunctionName. Он объявляется в блоке вспомогательных выражений между тегами <#+ и #>.
Вспомогательные методы обязательно должны находиться в конце шаблона. Блок вспомогательных методов может содержать также блоки выражений, кода, текста - как и в самом шаблоне. Результатом работы примера будет следующее:
public class MyClass { public void DoActionOne() { } public void DoActionTwo() { } }
Директивы
Директивы служат для установки правил обработки конкретного файла шаблона. Синтаксис директив имеет следующий формат:
<#@ ИмяДирективы [ИмяАттрибута = "ЗначениеАттрибута"] ... #>
Каждая из директив имеет свое предназначение. Имя директивы указывается один раз в самом начале и принимает одно из следующих значений:
- Template,
- Parameter,
- Output,
- Assembly,
- Import,
- Include.
Также можно создавать пользовательские директивы. Секция [ИмяАттрибута = "ЗначениеАттрибута"] может указываться один и более раз. Давайте рассмотрим директивы поподробнее.
Директива template
Директива Template указывает среде правила обработки файла шаблона и имеет следующий формат:
<#@ template [language="VB"] [hostspecific="true"] [debug="true"] [inherits="templateBaseClass"] [culture="code"] [compilerOptions="options"] #>
Все атрибуты являются необязательными. Параметр language указывает язык, на котором пишется код в шаблоне. Может принимать значения "C#" или "VB". Атрибут compilerOptions определяет условия компиляции. Атрибут culture указывает на культурную среду. Атрибут debug указывает, нужна отладка или нет. С помощью атрибута inherits можно указать, что код файла шаблона может наследоваться от другого класса, который, в свою очередь, тоже может быть сгенерирован из другого шаблона.
Установка атрибута hostspecific в значение true позволяет коду шаблона получить доступ к API Visual Studio. В коде шаблона становится возможным указывать относительный путь к файлам проекта, а не абсолютный. Также можно получить доступ из шаблона к свойствам класса Host, например, к дате и времени:
<#= Host.CurrentDateTime #>
Рассмотрим следующую директиву
<#@ template language="С#" hostspecific="true" debug="true" #>
Эта директива указывает, что код шаблона будет реализован на языке C# и нужен доступ к API Visual Studio. Включается отладчик для поиска и устранения ошибок в шаблоне.
Директива parameter
Директива parameter позволяет указывать параметры, которые могут передаваться в шаблон извне, и имеет следующий формат:
<#@ parameter type="ИмяТипа" name="Имя параметра" #>
Директива parameter доступна только начиная с Visual Studio 2010/.NET 4. Однако, во всех версиях Visual Studio можно передавать параметры в шаблон добавлением значений в класс CallContext.
Директива parameter имеет обязательные атрибуты type и name. Атрибут type передает название типа параметра. При этом должно указываться полное имя типа, например System.string, а не string. Этот пример передает строковый параметр TableName:
<#@ parameter type="System.string" name="TableName"#>
После включения параметров их можно использовать внутри шаблона как переменные.
Директива output
Директива output позволяет указать расширение и кодировку выходного файла. Расширение можно указать любое, по умолчанию принимается значение ".cs". Кодировка может принимать такие значения, как "utf-8", "us-ascii", "utf-32" и т.д. Имеет следующий формат.
<#@ output extension=".расширение" [encoding="обозначениеКодировки"] #>
Директива assembly
Директива assembly позволяет загрузить сборку и применять классы и методы этой сборки в шаблоне. Если нужна сборка из состава .Net, то можно просто указать имя сборки. Для других сборок надо указать полный путь к файлу сборки. Формат директивы assembly следующий:
<#@ assembly name="[ИмяСборки|ПутьКФайлу]" #>
Директива import
Формат директивы import:
<#@ import namespace="ПространствоИмен" #>
Эта директива позволяет импортировать пространства имен. В коде шаблона можно будет использовать простые имена классов вместо полных имен.
Директива include
Формат директивы include:
<#@ include file="filePath" #>
Эта директива позволяет включить в шаблон содержимое другого файла. Включаемый файл может содержать как шаблонный текст, так и команды обработки. Также он может иметь расширение, отличное от ".tt", например ".txt" или ".t4". Если в двух файлах шаблона есть схожие участки, можно в отдельном файле хранить общую часть шаблонов. А в оригинальных файлах директивы включения надо поставить вместо повторяющихся участков кода. Позже, при необходимости можно менять только содержимое включаемого файла.