海の底のブログ

プログラムでプログラムを自動生成する

タイトルと写真でT4 Text Templateの話なんだなと一発で分かりますね。

どうしてこうなったと思わなくもないですが、仕事でプログラムを書いていると時々同じような処理を繰り返し書くという事態に遭遇することがあります。

大抵はスタブやプロパティの転記、Excel定義書からのエンティティクラスの書き起こし等々、元ネタも決まってるので基本的に怠惰(爆)なプログラマーは速攻で自動生成しようとします。

が、周りを見渡すと私のような怠惰な人間はあんまりいないのか結構な頻度で手書きで書いてたりします。

なので何番煎じか分かったもんじゃありませんが、プログラムでプログラムを自動生成する方法について書きたいと思います。

※ちなみに個人的にはExcel定義書に関してはこっちをコードから自動生成したいですが、今回は置いときます。

基本的な考え方

例えばExcel定義書からエンティティを書き起こす場合だと、以下のような定義を延々書く羽目になると思います。

※今回はCoffeeScriptじゃなくてC#です。

/// <summary>
/// 社員コード
/// </summary>
public int EmployeeCode{ get; set; }

この場合ですとおそらくExcel定義書には名前と型とIDくらいは最低限書いてあるんだと思いますが、項目が100も200もあった日にはC#の自動プロパティで少し楽になった程度では割に合わないくらい手間がかかります。

というか私の場合手で書いたら途中で絶対ミスる。

この例の場合、“社員コード”、“int”、“EmployeeCode”以外は定型です。

ですので、何らかの手段でExcelの中身を読み込めば以下のような処理で出力できます。

※今回Excelのデータを読み込む処理自体は割愛します。

foreach(Field field in fields){
Console.WriteLine("/// <summary>");
Console.WriteLine("/// {0}", field.Name);
Console.WriteLine("/// </summary>");
Console.WriteLine("public {0} {1}{{ get; set; }}", field.Type, field.Id);
}

このように定型の部分を抽出してプログラムで出力しようというのが基本の考え方です。

T4 Text Template

で、上記のようにプログラムを書いてもいいんですが、書式が変わる度にいちいちプログラムを再コンパイルするのも面倒です。

なので面倒でないようにVisual Studioにはそのための仕組みが用意されています。それがT4 Text Template。

使い方は簡単。プロジェクトに新しい項目の追加からテキストテンプレートを選んで新規作成します。ちなみに拡張子は.ttです。

T4 Templateに先ほどのプログラムを移植すると以下のようになります。

せっかくなのでクラス定義丸ごと生成するようにしてみました。

<#@ 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" #>
<#
/* 項目の定義を取得する */
#>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace foo.dto {
public class <#= className#>
{
<# Generate(fields, (field)=>{#>
/// <summary>
/// <#= field.Name #>
/// </summary>
public <#= field.Type #> <#= field.Id #>{ get; set; }
<#});#>
}
}
<#+
void Generate<T>( IEnumerable<T> seq, Action<T> action )
{
foreach( var item in seq ) { action(item); }
}
#>

ざっくり解説すると地の文はそのまま出力され、<#= #>は変数の中身に置換されます。

また、<# #>で囲まれた部分はC#のプログラムとして実行されるのでこれを駆使してコードを生成します。

<#+ #>で囲まれた部分はクラス定義としてメソッド等が定義できます。ここではGenerateという要素の集合を出力するためのメソッドを定義しています。

この手法はこちらで紹介されていて便利なのでそのまま使っています。

<#@ assembly name=""#>はその名の通りアセンブリでTemplate内でロードするクラスライブラリを指定します。GACに登録されていれば名前を指定するだけで読み込めます。

<#@ import namespace=""#>usingと同じ意味です。

基本的にC#で出来ることは全部出来るので例えば<#@ assembly name=""#>で外部のライブラリを使ってExcelファイルを読み込む等々かなり複雑なことまで出来ます。

まとめ

なかなか日々の作業に追われていると、とにかく数をこなそうと頑張りがちです。

ただ、あまりに効率が悪いうえに頑張ったあげくにミスとか悲しすぎるので、楽しようよ。(魂の叫び)