はじめに
コーディングをする中で T4を使うシーンが出たため, 備忘録代わりに記事にしていきます.
T4とはなんぞや??という方は, 公式の概要ページを一読してみてください.
今回は, 2種類ある T4テキストテンプレートの中の デザイン時T4テキストテンプレートについて記述します.
かなり部分的な内容しか記述しませんが, 今回の記事内容のことを把握していればやりたいことはだいたいできると思います.
もし, やりたいことに対して記事内容が不足している場合は
辺りを参考にしていただくと、やりたいことを実現するための情報が得られるかもしれないです.
今回の目標
こういう練習にはすでに存在しているものを再発明するのがわかりやすくて良いので, Actionを自前実装した MyActionというものを作っていきたいと思います.
ご存知の方が多いとは思いますが, Actionの定義は以下のような煩雑なコードの繰り返しとなっているため, 手で書くのは非常に面倒です.
そこで今回はこれを T4を利用して作成してみます.
イメージとしては, 以下のようなコードが生成されるのが今回のゴールです.
namespaceT4TemplatePractice{delegatevoidMyAction<inT>(Tt);delegatevoidMyAction<inT1,inT2>(T1t1,T2t2);//// Similar codes...//}T4テキストテンプレート
1. T4テキストテンプレートをプロジェクトに追加する
プロジェクトを右クリックして, 新しい項目の追加から テキストテンプレートを追加します.
今回は MyActionTemplate.ttという名称で追加しました.
無事追加できればプロジェクト以下にファイルが追加されると思います.
2. T4テキストテンプレートを編集する
1. outputファイルを .csファイルにする
MyActionTemplate.ttを開くと以下のようになっていると思います.
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>
この最終行の output extensionの値を変更することで, 出力時の拡張子を変更することができます.
今回はもちろん .csとします.
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #> // <-- ここを修正
また, 今回は利用しませんが System.IOなどをテンプレート作成時に利用したい場合, import namespaceを追加してあげます.
// ================================================================
// 以下はあくまでもサンプルなので, コードを追加する必要は一切ありません
// ================================================================
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.IO" #> // <-- ここらへんに追加
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
修正した状態で上書き保存をすると, 以下のように MyActionTemplate.csが .ttに紐づく形で追加されると思います.
もし, 紐づく形で表示されない場合は, 一度 MyActionTemplate.csを削除したあとに, MyActionTemplate.ttを上書き保存をすると正しく表示されると思います.
2. テンプレート内で利用する変数・メソッドを定義する
テンプレート内では定義されている 変数や メソッドを利用することが可能です.
これらは利用したいときに定義可能ですが, 情報が散乱すると後でテンプレートを修正するときに泣きをみるので, おとなしく一まとめにしておきましょう.
今回は最上部で一括定義することにします.
定義する際には コントロールブロックと呼ばれる <# ~ #>で囲まれた中に C#コードを記述していきます.
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<# // ここから追加 --->
// 型パラメータの開始番号
var startNumber = 1;
// 型パラメータの最大番号
var maxNumber = 16;
// 型パラメータ生成用メソッド
string CreateGenericTypes(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"in T{i}"));
// パラメータ生成用メソッド
string CreateParameters(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"T{i} t{i}"));
#> // <--- ここまで追加
3. テンプレートを記述する
それでは, 実際に出力するテンプレートを記述していきます.
と言っても大部分は通常の .csと同様に記述ができます.
試しに, 以下のような普通のコードを記述し, 上書き保存してみましょう.
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<#
// 型パラメータの開始番号
var startNumber = 1;
// 型パラメータの最大番号
var maxNumber = 16;
// 型パラメータ生成用メソッド
string CreateGenericTypes(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"in T{i}"));
// パラメータ生成用メソッド
string CreateParameters(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"T{i} t{i}"));
#>
// ここから追加 --->
namespace T4TemplatePractice
{
public class Foo
{
public int Bar { get; set; }
}
}
// <--- ここまで追加
そうすると, MyActionTemplate.csに以下のようにそのままC#のコードが出力されていると思います.
namespaceT4TemplatePractice{publicclassFoo{publicintBar{get;set;}}}この普通の C#コードと T4テンプレート用の記述方法を組み合わせることで, MyActionを作成します.
それではまず 1~16 までの値をループするためのコードを追加します.
これには先ほどの コントロールブロックを利用します.
// (中略)
<#
// 型パラメータの開始番号
var startNumber = 1;
// 型パラメータの最大番号
var maxNumber = 16;
// 型パラメータ生成用メソッド
string CreateGenericTypes(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"in T{i}"));
// パラメータ生成用メソッド
string CreateParameters(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"T{i} t{i}"));
#>
// ここから追加 --->
namespace T4Practice
{
// 以下のコントロールブロック内でも, 上の方のコントロールブロックで定義した変数・メソッドを使える
<# for (int i = startNumber; i <= maxNumber ; i++) { #>
// do something
<# } #>
}
// <--- ここまで追加
コントロールブロックを利用することで, ブロック内に C# の forループ を記述することができます.
もちろん foreachループ や if文 などの制御構文を利用することもできます.
}もコントロールブロックで囲む必要があることに注意してください.
では, do somethingの位置に, 実際に MyActionを定義してみます.
// (中略)
<#
// 型パラメータの開始番号
var startNumber = 1;
// 型パラメータの最大番号
var maxNumber = 16;
// 型パラメータ生成用メソッド
string CreateGenericTypes(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"in T{i}"));
// パラメータ生成用メソッド
string CreateParameters(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"T{i} t{i}"));
#>
namespace T4Practice
{
<# for (int i = startNumber; i <= maxNumber ; i++) { #>
// ここから追加 --->
delegate void MyAction<<#= CreateGenericTypes(startNumber, i) #>>(<#= CreateParameters(startNumber, i) #>);
// <--- ここまで追加
<# } #>
}
型パラメータ と パラメータ を作成するためにそれぞれメソッドを利用しています.コントロールブロックでは メソッドの結果を利用することができないため, ここでは 式コントロールブロックと呼ばれる <#= ~ #>で囲まれた式の結果を文字列として出力する機能を利用しています.
上記のような変更を加えてから上書き保存をすることで, MyActionTemplate.csは以下のようになっていると思います.
namespaceT4TemplatePractice{delegatevoidMyAction<inT1>(T1t1);delegatevoidMyAction<inT1,inT2>(T1t1,T2t2);delegatevoidMyAction<inT1,inT2,inT3>(T1t1,T2t2,T3t3);delegatevoidMyAction<inT1,inT2,inT3,inT4>(T1t1,T2t2,T3t3,T4t4);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5>(T1t1,T2t2,T3t3,T4t4,T5t5);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14,inT15>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14,T15t15);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14,inT15,inT16>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14,T15t15,T16t16);}これで引数を必要とする MyActionは定義できましたが, 引数がいらない MyActionは定義できていないので, 最後に一行差し込んでおきます.
// (中略)
<#
// 型パラメータの開始番号
var startNumber = 1;
// 型パラメータの最大番号
var maxNumber = 16;
// 型パラメータ生成用メソッド
string CreateGenericTypes(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"in T{i}"));
// パラメータ生成用メソッド
string CreateParameters(int start, int count)
=> string.Join(",", Enumerable.Range(start, count).Select(i => $"T{i} t{i}"));
#>
namespace T4Practice
{
// ここから追加 --->
delegate void MyAction();
// <--- ここまで追加
<# for (int i = startNumber; i <= maxNumber ; i++) { #>
delegate void MyAction<<#= CreateGenericTypes(startNumber, i) #>>(<#= CreateParameters(startNumber, i) #>);
<# } #>
}
for文の外に追記していることに注意してください.
上記の変更を加えてから上書き保存をすることで, MyActionTemplate.csは以下のようになっていると思います.
namespaceT4TemplatePractice{delegatevoidMyAction();delegatevoidMyAction<inT1>(T1t1);delegatevoidMyAction<inT1,inT2>(T1t1,T2t2);delegatevoidMyAction<inT1,inT2,inT3>(T1t1,T2t2,T3t3);delegatevoidMyAction<inT1,inT2,inT3,inT4>(T1t1,T2t2,T3t3,T4t4);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5>(T1t1,T2t2,T3t3,T4t4,T5t5);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14,inT15>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14,T15t15);delegatevoidMyAction<inT1,inT2,inT3,inT4,inT5,inT6,inT7,inT8,inT9,inT10,inT11,inT12,inT13,inT14,inT15,inT16>(T1t1,T2t2,T3t3,T4t4,T5t5,T6t6,T7t7,T8t8,T9t9,T10t10,T11t11,T12t12,T13t13,T14t14,T15t15,T16t16);}これで MyActionの完成です!
おわりに
今回紹介した機能は T4テキストテンプレート機能のごくごく一部のものだけです.
もっと詳しく知りたい方は, 公式リファレンスを熟読することをオススメします.




