Кодогенерация T4
Нет, это не про Терминатора
Это про использование T4 Text Template Transformation Toolkit, встроенного в Visual Studio 2008 для автоматической генерации кода.
Кратко задача – есть исходники. Опять-то таки сгенерированные, но другим инструментом, Thrift.
Код на C#, публичные поля и свойства в классах с одинаковым именем, только различаются регистром.
Код необходимо использовать из VB.NET. Упс! VB.NET нечувствителен к регистру! Приплыли.
Исходники конечно есть, но они регулярно обновляются – так что их исправлять нельзя.
Классы не помечены partial – расширить напрямую тоже нельзя.
Но у нас же есть extension методы – спасибо .NET 3.5! Можно понаписать методов (эх… пока только методы, эктеншен свойств нет) с названием совпадающим со свойствами, но с каким-нибудь префиксом, подчеркиванием например.
Ок, хорошо. Но вручную писать обертки на 20 классов?! Да они еще, как я сказал, могут обновиться в будущем. Тут нужна автоматизация… И в VS 2008 она уже встроена – движок кодогенерации T4.
Файлы с расширением tt. Синтаксис очень похож на ASP.NET, только исполняются внутри Visual Studio (и не только, хостом может выступать любое приложение, и ваше в том числе).
И вот всё волшебство:
<#@ assembly name=“EDAM.dll” #>
<#@ assembly name=“System.Core” #>
<#@ import namespace=“System.CodeDom” #>
<#@ import namespace=“System.CodeDom.Compiler” #>
<#@ import namespace=“System.IO” #>
<#@ import namespace=“System.Linq” #>
<#@ import namespace=“System.Reflection” #>
<#@ import namespace=“Evernote.EDAM.Type” #>
// Autogenerated by <#= Host.GetType() #>
// <#= DateTime.Now #>
// DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
namespace Evernote.EDAM.Type
{
<# foreach (var @class in Assembly.GetAssembly(BaseClass)
.GetExportedTypes()
.Where(type => !type.IsNested)
.Where(type => type.Namespace == BaseClass.Namespace)) { #>
public static class <#= @class.Name #>Ex
{
<# foreach (var prop in @class.GetProperties()) { #>
public static <#= PrintType(prop.PropertyType) #> _<#= prop.Name #>(this <#= prop.DeclaringType #> value)
{
return value.<#= prop.Name #>;
}
<# }#>
}
<# } #>
}
<#+ Type BaseClass = typeof(Note); #>
<#+
public string PrintType(Type type)
{
var typeExpr = new CodeTypeReferenceExpression(type);
var csProvider = Microsoft.CSharp.CSharpCodeProvider.CreateProvider(“C#”);
var writer = new StringWriter();
csProvider.GenerateCodeFromExpression(typeExpr, writer, new CodeGeneratorOptions());
return writer.ToString();
} #>
Вкратце, по шагам:
- Перебираем все классы из сборки, из нужного пространства имен.
- Для каждого класса генерируем статический класс с таким-же именем и суффиксом Ex.
- Перебираем все свойства класса.
- Генерируем экстеншен-метод нужного типа с именем как оригинальное ствойство, но с префиксом _.
- Для генерация имени нужного типа используется маленькая хитрость. Так как по простому дженерики будут выводиться в IL-нотации, т.е. например System.Generic.List’1[System.String]. И это не будет компилироваться. Надо System.Generic.List<System.String> (для C#). Что и делается через CodeDom. Так как сборка на C# – то и провайдер для C# используется. Можно генерировать и в VB. Как в CodeDom, так и в самом T4 кстати.
Всё, теперь при сборке будет генерироваться набор расширений для каждого нужного класса с методами дублирующими все свойства, которые уже без проблем можно использовать в VB.NET. Кстати, в VB можно опускать скобки при вызове метода, если он не принимает параметров, что делает код еще более изящным (насколько вообще можно говорить об изящности в этой ситуации):
Полезные ссылки:
- T4: Text Template Transformation Toolkit
- Generating Artifacts By Using Text Templates (раздел в MSDN)
- Visual T4 (плагин для редактирования файлов tt с подсветкой)

Как часто такая задача возникает и что это за проекты, если не секрет?
Мне как-то сомнительной кажется необходимость перепрыгивать из C# в VB. Ладно еще, если бы речь про Smalltalk речь шла, или Eiffel…
terR0Q
Ноябрь 9, 2009 в 10:21
Проекты для веба.
Задачи довольны часты для всяких заготовок темплейтов вывода бизнес-моделей.
VB.NET удобен для xml-темплейтов из-за XML Literals прямо в коде (особенно в комбинации с LINQ).
der Igel
Ноябрь 9, 2009 в 10:47
А, об этом не подумал. Ну если проект заточен на работу с xml, или речь об отдельных модулях с такой же задачей (и в отдельных сборках), то это удобно. А вот весь проект под VB только ради легкости инлайн-разметки я бы подводить не стал.
terr0q
Ноябрь 9, 2009 в 11:39