Feel Good.

22 декабря 2010

Используем Reflection в T4 (Text Template Transformation Toolkit)

Недавно, мне была поставлена следующая задача: для каждого класса из стороннего API, разработать обертку (wrapper class), для удобства использования стороннего API. Оптимальное решение подобных задач всегда использовать кодогенерацию, а наиболее удобный инструмент для этого, это T4 (Text Template Transformation Toolkit). Так как набор классов находился в уже скомпилированной сборке, то действовать надо было только через System.Reflection. Как выяснилось позже, что при работе со сборкой через reflection в T4 возникает проблема блокировки, загружаемой через Assembly.Load, сборки. Stackoverflow подсказал, что для решения данной проблемы нужно воспользоваться FxCop API, предоставляющий достаточный инструментарий для работы со сборкой на уровне Reflection.

На момент написания статьи была доступна версия 1.35, релиз которой доступен здесь. Не буду вдаваться в подробности моей задачи, а просто приведу простой Hello-world пример работы с FxCop библиотекой. Итак после установки FxCop, добавим в наш проект новый T4 файл (TestTemplate.tt) содержащий следующий код:

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension=".T4.txt" #>
<#@ assembly name="C:\Program Files\Microsoft FxCop 1.35\FxCopSdk.dll" #>
<#@ assembly name="C:\Program Files\Microsoft FxCop 1.35\Microsoft.Cci.dll" #>
<#@ import namespace="Microsoft.Cci" #>
<#
string assemblyPath = @"...путь к нашей сборке...";

// Загрузка сборки без блокировки (аналог Assembly)
AssemblyNode assembly = AssemblyNode.GetAssembly(assemblyPath);

// Указываем какое пространство имен
Identifier namespaceId = Identifier.For(
"SomeNamespace");

// Указываем какой тип (у нас это имя класса)
Identifier typeId = Identifier.For(
"SomeType");

// Получаем полную информацию о типе (аналог System.Type)
TypeNode type = assembly.GetType(namespaceId, typeId);

// Перечислим все члены типа (свойства, методы, события)
foreach(Member member in type.Members)
{
// Публичный ли член
if(member.IsPublic)
{
WriteMember(member);
}
}
#>
<#+

void WriteMember(Member member)
{
// Если член является свойством
if(member is Property)
WriteProperty(member
as Property);
}

void WriteProperty(Property property)
{
// Получим имя свойства (полное имя, имя)
Identifier name = property.Name;

// Получим CLR тип свойства System.Type.
System.Type type = property.Type.GetRuntimeType();

// Форматируемый вывод.
WriteLine(
"Property '{0}' type of '{1}'", name, type);
}

#>

Выполнив "Run Custom Tool" на шаблоне T4 мы получим текстовый файл (TestTemplate.T4.txt), в котором перечилены все публичные свойства указанного типа, с указанием имени свойства и его CLR типа:

Property 'SomeProperty' type of 'System.Int32'


Так как сейчас я не генерировал код, я просто вывел результат в обычный текстовый файл.
Оригинальный тип:

namespace SomeNamespace

{

    public class SomeType

    {

        public int SomeProperty

        {

            set; get;

        }

 

        public void SomeMethod()

        {

        }

    }

}


Дальше можно усложнить нашу разметку. Например, разметка в виде C# кода, для получения исходника, или разметка в виде XML, CSV и другие.

Ссылки:
  1. T4 architecture
  2. What are the ramifications of using System.Reflection.Assembly.Load(System.IO.File.ReadAllBytes(path)) in T4?
  3. System.Reflection.Assembly.LoadFile Locks File

5 комментариев:

  1. В конце шаблона рекомендую ещё вызвать assembly.Dispose(), иначе, как показывает практика, иногда блокируются pdb файлы, что мешает сборке.

    ОтветитьУдалить
  2. @MeF
    Пока блокировок не замечал, но спасибо за совет, явный вызов Dispose никогда не будет лишним.

    ОтветитьУдалить
  3. Тоже сейчас пишу кодогенератор в VS2010 и у меня сборка блокируется после каждого выполнения T4. MeF, Спасибо за совет.

    ОтветитьУдалить
  4. Я бы для решения подобной задачи воспользовался бы Mono::Cecil, вместо FxCop

    ОтветитьУдалить
  5. @Stanislavus
    Спасибо, надо будет глянуть что это такое. Пока я слышал что это AOP framework.

    ОтветитьУдалить