Информация об изменениях

Сообщение Re: А кто у нас по T4 спец? от 01.04.2016 17:49

Изменено 01.04.2016 18:40 IT

Здравствуйте, Sinix, Вы писали:

S>UPD Мда, с тех пор как я в нём копался, на T4 сделали отличную документацию. Вроде все вопросы, кроме "что ставить из расширений?" отпали.


Если есть решарпер, то однозначно его плагин. Если нет то из всего остального глючного по выбору.

S>1. Есть файл \src\Assertions\Code.cs.


Посмотрел на этот класс, его тоже можно легко сгенерировать

S>2. Надо рядышком положить DebugCode.tt, который будет копировать содержимое Code.cs, добавлять к имени класса префикс Debug и заменять


Считать файл с использованием Host.ResolvePath. Сделать необходимые замены. Сохранить с помощью WriteLine. Всё.

S>4. Очень желательно, чтобы шаблон можно было использовать в нескольких местах (T4 вроде бы умеет include?)


Без проблем. Пишешь всю трансформацию ввиде метода вот в таких скобках <#+ ... #>, подключаешь через include.

S>Чтобы если в будущем появился другой класс ассертов, можно было сослаться на тот же шаблон, а не копипастить и не поддерживать правки в обоих копиях.


Можно не только асертов. Если туда передавать имя файла и список трансформаций, то будет применимо для чего угодно.

S>Сам что-то похожее уже делал, но результат получился абсолютно неподдерживаемый. Да и T4 с тех пор основательно подзабыл.


Чтобы больше не подзабывать проще один раз заглянуть внутрь. T4 — это простейший генератор, вся ценность которого состоит в том, что он входит в студию из коробки. А внутри у него вот что.

Создаём такой T4:

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
class MyClass
{
<#
    foreach (var item in new[] { 1, 2, 3 })
    {
#>
        int _v1<#= item #> = <#= GetValue1(item) #>;
        int _v2<#= item #> = <# GetValue2(item); #>;
<#
        WriteLine("\t\tint _v3{0} = {1};", item, 3 + item);

        GenerationEnvironment
            .AppendFormat("\t\tint _v4{0} = 3 + {0};", item)
            .AppendLine();
    }

    #>--------------------<#

    while (GenerationEnvironment[GenerationEnvironment.Length - 1] == '-')
        GenerationEnvironment.Length--;
#>
}

<#+
static int GetValue1(int i)
{
    return 3 + i;
}

void GetValue2(int i)
{
    #>3 + <#= i #><#+
}

public class MyClass
{
     
}
#>


Получаем такой результат:

class MyClass
{
        int _v11 = 4;
        int _v21 = 3 + 1;
        int _v31 = 4;
        int _v41 = 3 + 1;
        int _v12 = 5;
        int _v22 = 3 + 2;
        int _v32 = 5;
        int _v42 = 3 + 2;
        int _v13 = 6;
        int _v23 = 3 + 3;
        int _v33 = 6;
        int _v43 = 3 + 3;
}


Теперь идём в C:\Users\{Account}\AppData\Local\Temp\ и находим самый свежий .cs файл.

namespace Microsoft.VisualStudio.TextTemplating4ED5CCFF16531F2DB9FFA1638F6F33A42F0D66D58DE141248E97778336307295858447589B73CEF4BCD41D84119D13550D141744006D5E9FCC05ED0C4F590278
{
    using System;
    
    /// <summary>
    /// Class to produce the template output
    /// </summary>
    
    #line 1 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
    public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.TextTransformation
    {
#line hidden
        /// <summary>
        /// Create the template output
        /// </summary>
        public override string TransformText()
        {
            try
            {
                this.Write("class MyClass\r\n{\r\n");
                
                #line 5 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

    foreach (var item in new[] { 1, 2, 3 })
    {

                
                #line default
                #line hidden
                this.Write("\t\tint _v1");
                
                #line 9 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(item));
                
                #line default
                #line hidden
                this.Write(" = ");
                
                #line 9 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(GetValue1(item)));
                
                #line default
                #line hidden
                this.Write(";\r\n\t\tint _v2");
                
                #line 10 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(item));
                
                #line default
                #line hidden
                this.Write(" = ");
                
                #line 10 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
 GetValue2(item); 
                
                #line default
                #line hidden
                this.Write(";\r\n");
                
                #line 11 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

        WriteLine("\t\tint _v3{0} = {1};", item, 3 + item);

        GenerationEnvironment
            .AppendFormat("\t\tint _v4{0} = 3 + {0};", item)
            .AppendLine();
    }

    
                
                #line default
                #line hidden
                this.Write("--------------------");
                
                #line 19 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"


    while (GenerationEnvironment[GenerationEnvironment.Length - 1] == '-')
        GenerationEnvironment.Length--;

                
                #line default
                #line hidden
                this.Write("}\r\n\r\n");
            }
            catch (System.Exception e)
            {
                e.Data["TextTemplatingProgress"] = this.GenerationEnvironment.ToString();
                throw new System.Exception("Template runtime error", e);
            }
            return this.GenerationEnvironment.ToString();
        }
        private global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost hostValue;
        /// <summary>
        /// The current host for the text templating engine
        /// </summary>
        public virtual global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost Host
        {
            get
            {
                return this.hostValue;
            }
            set
            {
                this.hostValue = value;
            }
        }
        
        #line 26 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

static int GetValue1(int i)
{
    return 3 + i;
}

void GetValue2(int i)
{
    
        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
this.Write("3 + ");

        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(i));

        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

}

public class MyClass
{
     
}

        
        #line default
        #line hidden
    }
    
    #line default
    #line hidden
}


Это то, что сгенерировано T4.

Ключевые моменты:

— Имеем один сгенерированный класс GeneratedTextTransformation, наследованный от Microsoft.VisualStudio.TextTemplating.TextTransformation.
— Имеем один виртуальный метод TransformText.
— Всё, что находится внутри <# ... #> в нашем шаблоне попадает внутрь TransformText.
— Всё, что находится внутри <#+ ... #> включается в класс GeneratedTextTransformation в виде мемберов, включая объявленные классы.
— Генерируемый код — это всё, что вне <#(+) #> или внутри <#= #> как в старом ASP.NET.
— <#= №> — это по сути вызов метода Write базового класса TextTransformation.
— Методы Write, WriteLine можно вызывать напрямую в ассортименте.
— Их вызов, фактически заполняет StringBuilder, доступ к которому осуществляется напрямую через свойство GenerationEnvironment.
— Свойством GenerationEnvironment можно манипулировать напрямую как вздумается, как показано выше в примере.

В общем, тупая генерилка. Вся мощь инструмента зависит исключительно от ваших рук и фантазии.

Ну и про возможности partial классов тоже не забываем.

Если интересно посмотреть на примеры, то можно здесь https://github.com/linq2db/t4models/blob/master/Templates/DataModel.ttinclude
и результаты работы здесь https://github.com/linq2db/t4models/tree/master/Tests .
Re: А кто у нас по T4 спец?
Здравствуйте, Sinix, Вы писали:

S>UPD Мда, с тех пор как я в нём копался, на T4 сделали отличную документацию. Вроде все вопросы, кроме "что ставить из расширений?" отпали.


Если есть решарпер, то однозначно его плагин. Если нет то из всего остального глючного по выбору.

S>1. Есть файл \src\Assertions\Code.cs.


Посмотрел на этот класс, его тоже можно легко сгенерировать

S>2. Надо рядышком положить DebugCode.tt, который будет копировать содержимое Code.cs, добавлять к имени класса префикс Debug и заменять


Считать файл с использованием Host.ResolvePath. Сделать необходимые замены. Сохранить с помощью WriteLine. Всё.

S>4. Очень желательно, чтобы шаблон можно было использовать в нескольких местах (T4 вроде бы умеет include?)


Без проблем. Пишешь всю трансформацию ввиде метода вот в таких скобках <#+ ... #>, подключаешь через include.

S>Чтобы если в будущем появился другой класс ассертов, можно было сослаться на тот же шаблон, а не копипастить и не поддерживать правки в обоих копиях.


Можно не только асертов. Если туда передавать имя файла и список трансформаций, то будет применимо для чего угодно.

S>Сам что-то похожее уже делал, но результат получился абсолютно неподдерживаемый. Да и T4 с тех пор основательно подзабыл.


Чтобы больше не подзабывать проще один раз заглянуть внутрь. T4 — это простейший генератор, вся ценность которого состоит в том, что он входит в студию из коробки. А внутри у него вот что.

Создаём такой T4:

<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
class MyClass
{
<#
    foreach (var item in new[] { 1, 2, 3 })
    {
#>
        int _v1<#= item #> = <#= GetValue1(item) #>;
        int _v2<#= item #> = <# GetValue2(item); #>;
<#
        WriteLine("\t\tint _v3{0} = {1};", item, 3 + item);

        GenerationEnvironment
            .AppendFormat("\t\tint _v4{0} = 3 + {0};", item)
            .AppendLine();
    }

    #>--------------------<#

    while (GenerationEnvironment[GenerationEnvironment.Length - 1] == '-')
        GenerationEnvironment.Length--;
#>
}

<#+
static int GetValue1(int i)
{
    return 3 + i;
}

void GetValue2(int i)
{
    #>3 + <#= i #><#+
}

public class MyClass
{
     
}
#>


Получаем такой результат:

class MyClass
{
        int _v11 = 4;
        int _v21 = 3 + 1;
        int _v31 = 4;
        int _v41 = 3 + 1;
        int _v12 = 5;
        int _v22 = 3 + 2;
        int _v32 = 5;
        int _v42 = 3 + 2;
        int _v13 = 6;
        int _v23 = 3 + 3;
        int _v33 = 6;
        int _v43 = 3 + 3;
}


Теперь идём в C:\Users\{Account}\AppData\Local\Temp\ и находим самый свежий .cs файл.

namespace Microsoft.VisualStudio.TextTemplating4ED5CCFF16531F2DB9FFA1638F6F33A42F0D66D58DE141248E97778336307295858447589B73CEF4BCD41D84119D13550D141744006D5E9FCC05ED0C4F590278
{
    using System;
    
    /// <summary>
    /// Class to produce the template output
    /// </summary>
    
    #line 1 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
    public class GeneratedTextTransformation : Microsoft.VisualStudio.TextTemplating.TextTransformation
    {
#line hidden
        /// <summary>
        /// Create the template output
        /// </summary>
        public override string TransformText()
        {
            try
            {
                this.Write("class MyClass\r\n{\r\n");
                
                #line 5 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

    foreach (var item in new[] { 1, 2, 3 })
    {

                
                #line default
                #line hidden
                this.Write("\t\tint _v1");
                
                #line 9 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(item));
                
                #line default
                #line hidden
                this.Write(" = ");
                
                #line 9 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(GetValue1(item)));
                
                #line default
                #line hidden
                this.Write(";\r\n\t\tint _v2");
                
                #line 10 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
                this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(item));
                
                #line default
                #line hidden
                this.Write(" = ");
                
                #line 10 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
 GetValue2(item); 
                
                #line default
                #line hidden
                this.Write(";\r\n");
                
                #line 11 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

        WriteLine("\t\tint _v3{0} = {1};", item, 3 + item);

        GenerationEnvironment
            .AppendFormat("\t\tint _v4{0} = 3 + {0};", item)
            .AppendLine();
    }

    
                
                #line default
                #line hidden
                this.Write("--------------------");
                
                #line 19 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"


    while (GenerationEnvironment[GenerationEnvironment.Length - 1] == '-')
        GenerationEnvironment.Length--;

                
                #line default
                #line hidden
                this.Write("}\r\n\r\n");
            }
            catch (System.Exception e)
            {
                e.Data["TextTemplatingProgress"] = this.GenerationEnvironment.ToString();
                throw new System.Exception("Template runtime error", e);
            }
            return this.GenerationEnvironment.ToString();
        }
        private global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost hostValue;
        /// <summary>
        /// The current host for the text templating engine
        /// </summary>
        public virtual global::Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost Host
        {
            get
            {
                return this.hostValue;
            }
            set
            {
                this.hostValue = value;
            }
        }
        
        #line 26 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

static int GetValue1(int i)
{
    return 3 + i;
}

void GetValue2(int i)
{
    
        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
this.Write("3 + ");

        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"
this.Write(Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture(i));

        
        #line default
        #line hidden
        
        #line 34 "I:\linq2db.LINQPad\Source\TextTemplate1.tt"

}

public class MyClass
{
     
}

        
        #line default
        #line hidden
    }
    
    #line default
    #line hidden
}


Это то, что сгенерировано T4.

Ключевые моменты:

— Имеем один сгенерированный класс GeneratedTextTransformation, наследованный от Microsoft.VisualStudio.TextTemplating.TextTransformation.
— Имеем один виртуальный метод TransformText.
— Всё, что находится внутри <# ... #> в нашем шаблоне попадает внутрь TransformText.
— Всё, что находится внутри <#+ ... #> включается в класс GeneratedTextTransformation в виде мемберов, включая объявленные классы.
— Генерируемый код — это всё, что вне <#(+) #> или внутри <#= #> как в старом ASP.NET.
— <#= #> — это по сути вызов метода Write базового класса TextTransformation.
— Методы Write, WriteLine можно вызывать напрямую в ассортименте.
— Их вызов, фактически заполняет StringBuilder, доступ к которому осуществляется напрямую через свойство GenerationEnvironment.
— Свойством GenerationEnvironment можно манипулировать напрямую как вздумается, как показано выше в примере.

В общем, тупая генерилка. Вся мощь инструмента зависит исключительно от ваших рук и фантазии.

Ну и про возможности partial классов тоже не забываем.

Если интересно посмотреть на примеры, то можно здесь https://github.com/linq2db/t4models/blob/master/Templates/DataModel.ttinclude
и результаты работы здесь https://github.com/linq2db/t4models/tree/master/Tests .