T4テンプレートを作っているとコードハイライトもコード補完も効かない現状にうんざりしますよね?しない?
というわけで筆者が必死にない頭を使ってC#とT4を同一ファイルでコーディングできないかと考えました(!?)。
完成品?
とりあえずコードをどうぞ
/*<# GenerationEnvironment.Length -= 2; #>
<#@ import namespace="System.Collections.Generic" #>
<#//*///#><#/*
using System;
using System.Collections.Generic;
namespace SharpTemplate
{
//*/#>
/*<# GenerationEnvironment.Length -= 2; #><#+
//*///#><#+
class SharpTemplateClass
{
public static void test()
{
}
//#><#+/*
/*/#><#+
}
//*///#><#+/*
}
}
//*/#>
これでC#ソースとしてコンパイルできるしT4テンプレートとしてコンバートしてくれます、もちろんincludeもできます。
どうしてもC#とT4では解釈や記述が異なっていて共存できない部分もあるので、記述する箇所によってどっち片方が認識できてもう片方はコメント扱いで無視されるようになっています。
読めない?ではコメント付きで
でもあれじゃどこがどうなるのかさっぱり分からないですよね?というわけでどう認識されるのかがわかりやすいように無駄にがんばってコメントでゴテゴテに装飾してみました、
/*<# GenerationEnvironment.Length -= 2; #>
<#//++++++++++++++++++++++++T4のみ認識する領域+++++++++++++++++++++++++++++++++++#>
<#@ import namespace="System.Collections.Generic" #>
<#//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++#>
<#//*///#><#/*
//***************************C#のみ認識する領域***********************************
using System;
using System.Collections.Generic;
namespace SharpTemplate
{
//********************************************************************************
//*/#>
/*<# GenerationEnvironment.Length -= 2; #><#+
//=================T4のみ認識するクラス機能ブロック===========================
//=========================================================================
//*///#><#+
//----------------C#・T4共に認識するクラス機能ブロック------------------------
class SharpTemplateCommentClass
{
public static void test()
{
}
//----------------------------------------------------------------------------
//#><#+/*
/*/#><#+
//=================T4のみ認識するクラス機能ブロック===========================
}
//==========================================================================
/*--(A)--*/
//*///#><#+/*
//***************************C#のみ認識する領域***********************************
}
}
//*******************************************************************************
//*/#>
これで一目瞭然ですね。
ちなみに/*--(A)--*/の部分に以下のコードを挿入することで認識する対象を切り替えることも出来ます。
//*///#><#+ //C#・T4両方とも認識できるようになります
//*///#><#+/* //C#のみ認識できるようになります
//#><#+/* //T4のみ認識できるようになります(先頭に入れたり連続して挿入したりするとエラーになります)
/*/#><#+
コードをまとめられるならusingディレクティブ(importディレクティブ)もまとめよう
賢明な方なら既にお気づきのことかと思うのですが、せっかくC#とT4をまとめられたのだからusingディレクティブ(importディレクティブ)もコピペなしで1行でまとめて宣言できたらうれしいですよね?
でも、
//C#
using System.Collections.Generic;
//T4
<#@ import namespace="System.Collections.Generic" #>
というように書き方がまるっきり異なるんですよね・・・そんなこと出来るんでしょうか?
できます!!
こちらをご覧ください
/*<# GenerationEnvironment.Length -= 2;/*/using/*/#><#@ import namespace="/**/System.Collections.Generic;//"#>
すごくごちゃごちゃして読みづらいコードになっておりますが(最初からとかいうな)、これできちんとC#はusingディレクティブに、T4はincludeディレクティブとして認識してくれます(C#が解釈すべき部分の文字色に注目)。
なんでそうなるの?
端的に言うとC#とT4ではソースコードをパースする箇所が異なることと、C#のコメント外とコメント内でコメント文字列(//,/*,*/)の解釈が異なることを利用しています。
ソースコードのパースについて
C#はファイル全体をパースしますが、T4はT4ブロック(<# ~ #>,<#+ ~ #>)の中しかパースしません、なので下記のコードはC#とT4では扱いが異なります。
/*<# Write("C#ではただのコメントだけどT4では有効なC#コード"); #>*/
コメントはどうなるでしょう?下記のコードを見てください
//T4ではエラーになります
/*<#*/ Write("テスト"); /*#>*/
C#では全体がパースされますがT4は<# #>中のコメントしかパースしないので下記のように解釈されます
*/ Write("テスト"); /*
C#ではコメントの一部となっていたはずの文字列を解釈して文法エラーになっちゃってますね
コメント文字列の扱いについて
C#ではブロックコメントの中にあるコメント文字列は次のように扱われます
・// ブロックコメントの一部として扱われます
・/* ブロックコメントの一部として扱われます
・*/ ブロックコメントの終了として扱われます(=ブロックコメントのネストはできません)
また行コメントの中にあるコメント文字列は次のように扱われます
・/* 行コメントの一部として扱われます
・*/ 行コメントの一部として扱われますが、行コメントの前に閉じられていないブロックコメントの開始/*があった場合は
ブロックコメントの終了として扱われます
上記は知っている人なら当たり前の話なんですが、この仕様を応用することで前に/*(ブロックコメントの開始)があるかないかで解釈の異なる文字列を作ることが出来ます。やってみましょう
/* /*/ //前に/*があるとコメントの終了になります
/*/ */ //前に/*がないとコメントの開始になります
同じ/*/という文字列ですが、前に/*があればコメント終了、なければコメント開始とまるっきり反対の意味になります。直感では解りづらいかもしれませんがコメントの開始と終了を意識しながら1文字ずつ見ていけば何か見えてくるかもしれません。
このコメントとT4の仕様を応用してこんなことも出来ます。
//C#ではコメント外と解釈されて有効、T4ではコメント内と解釈されて無効
/*<#/*/Console.WriteLine("Test");/*/#>*/
そんな変な文字列が存在することがわかったところで筆者が発見した/*の有無で解釈が異なる文字列を羅列してみます。上記の亜種みたいなものですが・・・。
//*/ //何もなければただの行コメントですが、前に/*がある場合はブロックコメントの終了になります。
//*/// //上記だと/*がある場合//*/の後ろがコメント外になりますがこうすると後ろもコメントになります。
/**/ //何もなければただの短いブロックコメントですが、前に/*がある場合はブロックコメントの終了になります。
//上記2つとの違いは/**/の後ろは/*の有無にかかわらずコメント外になることです。
終わりに
この仕様を理解した上で最初のコードを眺めるとまた違ったように見えてくると思います。みえない?
というわけで楽しいT4ライフをお過ごしください。