Spis treści:

Kategoria:C#Edytor T4


Szablony tekstowe T4 - statyczny kod

Hello, World of T4!

Przyjęło się, że nauka każdego nowego języka programowania rozpoczyna się od programu Hello, World!Na pamiątkę wydarzenia, które miało miejsce w 1974 roku. W spółce Bell Laboratories Brian Kernighan, znana postać w programowaniu, stworzył kurs języka C. Jako pierwszy przykład umieścił on tam słynny kod wypisujący w oknie konsoli tekst hello, world. Małymi literami i bez wykrzyknika. Nikt by o tym pewnie nie usłyszał, gdyby nie wydana cztery lata później słynna książka The C Programming Language, współautorstwa Briana Kernighana i Dennisa Ritchie. Książka jest przez niektórych uznawana za matkę całej literatury języka C, ale także za matkę późniejszego standardu tego języka.. Nie inaczej będzie i w tym przypadku. Postaram się krok po kroku pokazać proces tworzenia bardzo prostego szablonu wypisującego statyczny tekst. Trudno będzie znaleźć praktyczne zastosowanie przykładu, ale to samo można powiedzieć o słynnym Hello, World! w C. Przejdźmy zatem do części właściwej. Ze względu na ograniczone wsparcie Visual Studio w zakresie kolorowanie składni i Intellisense do pracy z szablonami może się przydać Edytor szablonów tekstowych T4.

Dodanie szablonu T4 do projektu

Praca z szablonem T4 rozpoczyna się od tych samych czynności co praca z dowolnym plikiem projektu Visual Studio. Tworzymy nowy projekt, klikamy na nim prawym przyciskiem myszy i wybieramy Add/New Item...

Ilustracja przedstawia sposób dodawania szablonów do projektu Visual Studio
Dodawanie szablonu T4 do projektu Visual Studio

Plik nie jest pusty. Nie wszystkie linijki są jednak potrzebne i można je spokojnie usunąć. Ich znaczenie zostanie wyjaśnione wtedy, gdy okaże się to konieczne. Popatrzmy na przykład Hello, World! i przeanalizujmy go:

<#@ output extension=".txt" #>
Hello, World!

Szablony T4 mają proste założenia: wszystkie bloki wymagające przetworzenia znajdują się w znacznikach <#= #>, <#+ #>, <#@ #> lub <# #>. Każdy z nich ma swoje zastosowanie i tak:

  • <#= #> oznacza blok wyrażenia. To co zostanie wyliczone przez wyrażenie będzie wstawione do pliku wynikowego.
  • <#+ #> oznacza blok dodatków. Tutaj definiuje się funkcje i klasy, które nie powinny brać udziału w przekształceniach. To dobre miejsce na funkcje i klasy pomocnicze.
  • <#@ #> oznacza blok dyrektyw. To instrukcje dla całego silnika T4. Definiujemy tu typ pliku wyjściowego, referencje do zewnętrznych bibliotek, deklaracje używanych przestrzeni nazw.
  • <# #> oznacza blok kontrolny. Tu sterujemy całym procesem generowania szablonu. Można tu umieścić instrukcje warunkowe, pętle, deklaracje zmiennych. Wszystko co nie mieści się w innych blokach, najprawdopodobniej jest blokiem kontrolnym. Ten blok zostanie wykonany podczas sekwencyjnego przetwarzania szablonu.

Wróćmy zatem do przykładu Hello, World!. Pierwsza linijka to dyrektywa. Ta akurat wskazuje plik wynikowy, a dokładniej jego rozszerzenie. Nazwa pliku będzie taka sama jak nazwa szablonu. Jeżeli nasz plik ma nazwę ABC.tt, w wyniku przekształcenia otrzymamy plik ABC.txt. Druga linijka to tekst, który znajdzie się w pliku wynikowym. Każda bowiem wartość nie umieszczona w znacznikach T4 zostaje bezpośrednio przeniesiona do pliku wynikowego. Po zapisaniu pliku z rozszerzeniem *.tt (lub wywołaniu funkcji Run Custom Template) silnik T4 wygeneruje plik wynikowy a w nim - Hello, World!

Szablon T4 nie do tego jednak służy. Nie będziemy przecież generować statycznych tekstów! Popatrzmy na coś bardziej skomplikowanego.

Szablony T4 i pętle

Szablonów T4 używa się przede wszystkim tam, gdzie zmuszeni byliśmy do tworzenia powtarzalnych fragmentów kodu. Jeżeli coś ma być wykonane wiele razy, stosujemy pętle. Gdybyśmy zechcieli wypisać Hello, World! 5 razy, moglibyśmy zrobić coś takiego:

<#@ output extension=".txt" #>
<# for (int i = 0; i < 5; i++) { #>
Hello, World!
<# } #>

To samo można zrobić w nieco inny sposób:

<#@ output extension=".txt" #>
<# for (int i = 0; i < 5; i++)
    WriteLine("Hello, World!");
#>

Oba fragmenty wygenerują taki oto plik tekstowy:

Hello, World!
Hello, World!
Hello, World!
Hello, World!
Hello, World!

Czas na coś bardziej praktycznego. Spróbujmy wygenerować typ wyliczeniowy zawierający kolejne flagi bitowe. Można to zrobić na kilka sposobów, między innymi tak:

<#@ output extension=".cs" #>
public enum States
{
<#
for (int i = 0, bitValue = 1; i < 8; i++, bitValue <<= 1) { #>
    Bit<#=i#> = <#=bitValue#>,
<# } #>
}

Warto zwrócić uwagę, że zmienił się typ pliku wynikowego, z .txt na .cs. Po drugie, pojawił się nowy blok, blok wyrażenia. To, co stanie się wynikiem wyrażenia jest przekazywane do pliku wynikowego w postaci tekstowej. Ze zmiennej i zrobi się zatem tekst (ToString()) tworząc nazwy kolejnych pól wyliczeniowych Bit0, Bit1 i tak dalej. To samo można zrobić w nieco inny sposób:

<#@ output extension=".cs" #>
public enum States
{
<#
PushIndent("\t");
for (int i = 0, bitValue = 1; i < 8; i++, bitValue <<= 1)
    WriteLine("Bit{0} = {1},", i, bitValue);
PopIndent();
#>
}

Oba fragmenty wygenerują taki sam kod:

public enum States
{
    Bit0 = 1,
    Bit1 = 2,
    Bit2 = 4,
    Bit3 = 8,
    Bit4 = 16,
    Bit5 = 32,
    Bit6 = 64,
    Bit7 = 128,
}

To zwykły plik .cs, który zostaje automatycznie włączony do projektu i może być kompilowany z całą resztą programu. Mało tego, domyślnie akcja na tym pliku będzie ustawiona na Compile.

Generowanie plików skryptowych

Najprostszym sposobem przekazywania danych do szablonów jest umieszczenie ich w samym szablonie. Popatrzmy na przykład generowania klasy na podstawie kilku parametrów zdefiniowanych w samym szablonie:

<#@ output extension=".cs" #>
<# string[] config = {"FirstName", "LastName", "Age:int", "Wage:decimal"}; #>
public class T4Template
{
    public int ID { get; set; }    
<#
PushIndent("\t");
foreach (var property in config)
{
    string[] parts = property.Split(':');
    if (parts.Length > 1 )
        WriteLine("public {0} {1} {{ get; set; }}", parts[1], parts[0]);
    else
        WriteLine("public string {0} {{ get; set; }}", parts[0]);
}
ClearIndent();
#>
}

Znów, pomijając praktyczne zastosowania, przyjrzyjmy się całej konstrukcji. Definiujemy sobie listę atrybutów klasy, a na jej podstawie tworzymy klasę właściwą. Typ właściwości może, ale nie musi, być podany po dwukropku. Domyślnie będzie to string. Po transformacji otrzymamy taką oto klasę wynikową:

public class T4Template
{
    public int ID { get; set; }    
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public decimal Wage { get; set; }
}

Można pójść o krok dalej i wygenerować od razu skrypt dla języka SQL:

<#@ output extension=".cs" #>
<# string[] config = {"FirstName", "LastName", "Age:int", "Wage:decimal"}; #>
public class T4Template
{
    public int ID { get; set; }    
<#
PushIndent("\t");
foreach (var property in config)
{
    string[] parts = property.Split(':');
    if (parts.Length > 1 )
        WriteLine("public {0} {1} {{ get; set; }}", parts[1], parts[0]);
    else
        WriteLine("public string {0} {{ get; set; }}", parts[0]);
}
ClearIndent();
#>
}

/* ---Skrypt SQL---
CREATE TABLE T4Template
(
    ID int CONSTRAINT PK_T4Template_ID PRIMARY KEY CLUSTERED,
<#
PushIndent("\t");
for (int i = 0; i < config.Length; i++)
{
    string[] parts = config[i].Split(':');
    if (parts.Length > 1)
        Write("{0} {1}", parts[0], parts[1]);
    else
        Write("{0} nvarchar(50)", parts[0]);

    if (i == config.Length - 1)
        WriteLine("");
    else
        WriteLine(",");
}
ClearIndent();
#>
)
*/

Tym razem otrzymamy taki oto rezultat:

public class T4Template
{
    public int ID { get; set; }    
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public decimal Wage { get; set; }
}

/* ---Skrypt SQL---
CREATE TABLE T4Template
(
    ID int CONSTRAINT PK_T4Template_ID PRIMARY KEY CLUSTERED,
    FirstName nvarchar(50),
    LastName nvarchar(50),
    Age int,
    Wage decimal
)
*/

Powyższe rozwiązanie to tylko przykład możliwości szablonu T4. Pokazane rozwiązanie wymagałoby zastosowania odpowiednich mapowań typów, bo nie zawsze istnieje tak proste przełożenie jak int-int i decimal-decimal.

Podsumowanie

Celem artykułu było pokazanie szablonów T4 i ich sposobu działania. Trzeba wiedzieć, że to dopiero początek, a prawdziwa siła drzemie w ich prawie nieograniczonych możliwościach. Taki szablon może wykonywać wszystko to, na co pozwala .NET. To między innymi czytanie konfiguracji, wykonywanie zapytań do bazy danych, budowanie klas i skryptów na podstawie mechanizmu refleksji, tworzenie plików. O tym jednak napiszę innym razem. Mam nadzieję, że tym wpisem zachęciłem do poznania tego, moim zdaniem, bardzo przydatnego narzędzia.

Kategoria:C#Edytor T4

, 2014-05-16

Brak komentarzy - bądź pierwszy

Dodaj komentarz
Wyślij
Ostatnie komentarze
To samo pytanie co wyżej. Mam za zadanie dodać kolumnę do istniejącej tabeli łącząc obie inne kolumny ze sobą, ale nie mam pojęcia jak za to się zabrać
działa :) tylko była literówka :)
Podziękował. Trochę późno, po 8 latach, ale dzięki za testy (rozumiem że dla SQL2012 robione). Tak się właśnie zastanawiałem ile złego czynię stosując czasem __(max).
Super robota, korzystając z innych internetowych kalkulatorow po prostu wątpiłem w ich prawdomówność, w końcu trafiłem tutaj i wynik w końcu jest wiarygodny. 40 km w 2 h 810 kcal, ciekawostka: na fitatu wyliczyło mi 5700 kcal 😊 najlepiej będzie chyba jak kupię zegarek sportowy.
Wielkie dzieki za solidne wyjasnienia tematu.