kazuk は null に触れてしまった
C# / .NET 系技術ネタ縛りでお送りしております
月別アーカイブ: 8月 2010
T4からの脱獄キットリリースしました
2010/08/15
投稿者: : つい先日、「良く訓練されたT4使いは…」なんて事を書いてたりしましたが…
絶望していた ToStringHelper に死亡宣告を与える事に成功しましたので公開致します。
ReadMe.txt をべたっと貼り付け。英語の添削できるひとの突っ込み希望です。
ReadMe.txt に書いてある事ですけど、やっている事は T4 が作る .cs の中で ToStringHelper に対してもっとマットウな実装を作ってぶち当てる。
.cs に書いてあるコードと、参照アセンブリにあるコードでは .cs に書いてある方が優先されて使われるので結果としてToStringHelperのアホさかげんに付き合う必要なんてナッシングって事です。
this is a readme for using T4 Jail Braking Kit. (this readme is Generated by ReadMe.tt on this folder) 1. What Is Jail. T4 is output to <#=value #> to in TextTemplatingFileGenerator Write( Microsoft.VisualStudio.TextTemplating.ToStringHelper.ToStringWithCulture( value ) ) oops, static method call, can't be change to anything. in TextTemplatingFilePreprocessor Write( ToStringHelper.ToStringWithCulture( value ) ) this ToStringHelper.ToStringWithCulture is so STUPID and anything to do replace. ( ToStringHelper not partial and,ToStringWithCulture is not virtual ) path the null, throws ArgumentNullException, I write code!, write be "null"! path the boolean, "True", "False", I don't know VB. path the List, OK, you recommend <#@ output extension=".il" #>! I do everytime grep and replace 'Write( ToStringHelper.ToStringWithCulture(' to runs after TextTemplatingFilePreprocessor, to runs after, after, In result, I do brake the Jail, now! 2. Detail of Jail Brake. 2.1. in TextTemplatingFileGenerator, Kill the stupid ToStringHelper with type definition confrict, in type definition confircted , C# is prior .cs defined as referenced assembly defined. That's raise warning, but success fully compiled and run. '<#+ code #>' contexted, namespace with 'Microsoft.VisualStudio.TextTemplating(random value)' and TextTransformation class. <#+ } // exit class } // exit namespace namespace Microsoft.VisualStudio.TextTemplating // target stay in close!, ready to fire... { pubic partial class ToStringHelper // Fire!, All belettes to be use! collectry kill that! { // implement the more smart guy but, i can't return original context because namespace is named by rundom value, T4 is last of '<#+' and '#>' renders '}' for exit class,'}' for exit namespace. In result, i stay in my ToStringHelper class contxt, T4 is closing by default, and can be exetend to my ToStringHelper implementation by after include .tt's. ( dont be afraid, your tt's <#+ #> section is renders out after complete TextTransformation ,see Appendix B) Jail Braking require's only 2 line ,write below in last of your tt's head of directivs (see this file's top) <#@ include file="BrakeJail.ttlib" #> <# Microsoft.VisualStudio.TextTemplating.ToStringHelper.Initialize(this); #> i implements ToStringHelper for you, this is extensible and having Dictionary> your custom type formatters to add it. this implementation is little bit complex, because avoid boxing. if you int formatter added, int value was used by just int, no any type casting and not be place to object. reflection result cashe implemented transparently a expression compiled result cache, perfomance is justice! usage in example on head of file <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# ToStringHelper.AddFormatter( (bool b)=>b?"true":"false" ); #> <#= 0==1 #> result: false second choice, you handle event of ToStringHelper.ResolveFormatter this choice can be to do that, if type is array, create array formatter etc... in example, List formatter is shown below: <# EventHandler handler = (src, e) => { Type t = e.TypeForResolveFormatter; if( t.IsGenericType && t.GetGenericTypeDefinition()==typeof(List<>) ) { e.SetFormatter( t, GetListFormatter(t) ); } }; ToStringHelper.ResolveFormatter+=handler; #> and bottom of this file implement it: <#+ public static string ListFormatter( List list ) { return "new List<"+typeof(T).FullName+"> {" + string.Join( ",", (from item in list select ToStringHelper.ToStringWithCulture( item ) ) ) + "}" ; } public Delegate GetListFormatter( Type listType ) { Type[] genArgs = listType.GetGenericArguments(); var mi = this.GetType() .GetMethods() .Single( m=>m.Name=="ListFormatter" ) .MakeGenericMethod( genArgs[0] ); var listParam = Expression.Parameter( listType, "list" ); var exp = Expression.Lambda( Expression.Call( mi, listParam ), listParam ); return exp.Compile(); } #> I do it: <#= new List { 1,2,3 }#> result: new List {1,2,3} Note: reflection to Generic Methods pattern. method info of Method -> MakeGenericMethod -> Exp.Lambda( exp.Call ) -> .Compile() -> .Invoke() is standard of todays reflection to Generic Methods pattern. Delegate is sometime used Unknown type Lambda in code. if you use Delegate.Method.GetParameters() index 0 was sometime not real parameter thats ParameterType is System.Runtime.CompilerServices.ExecutionScope (in mscorlib 2.0), System.Runtime.CompilerServices.Closure (is mscorlib 4.0), in Delegate.Target==null GetParameters results parameters from index 0. in Delegate.Target!=null GetParameters results parameters from index 1, index 0 is Target's type. 2.2 in TextTemplatingFilePreprocessor I writing code now! may be as soon complete and show it. ---------------- Appendix. A. My .tt nameing rule. .tt designed for run with CustomTool .ttinc include file contains '<# #>' or else normal text. because output is created <#@ include #> only. .ttlib include file but only contents '<#+ #>' sections because no output <#@ include #> only. Note : T4's rule of .tt and included files output ordering (not MS specified and no source, this is only my trying some tests and results seems to be it. if MS specified it, send link to @kazuk on twitter) example: <#@ include filename="file1"#> <#@ include filename="file2"#> <#@ include filename="file3"#> my tt contents of normal text and '<# #>' sections my tt <#+ #> sections output does below order. 1. nomal text and '<# #>' sections from file1 2. nomal text and '<# #>' sections from file2 3. nomal text and '<# #>' sections from file3 4. my tt contents of normal text and '<# #>' sections 5. my tt <#+ #> sections 6. <#+ #> sections from file1 7. <#+ #> sections from file2 8. <#+ #> sections from file3 Appendix B. solution for 'Only one chance for override Dispose(bool) problem' on your include list file1.ttlib override Dispose(bool) and appeed expencive object property for tt file2.ttlib override Dispose(bool) and appeed expencive object property for tt ... thats can't be If you trade me the 'only on chance for override Dispose(bool)' include 'DisposeImplement.ttlib' on this archive Utilities folder. this has two protected field IList ExpenciveObjects IList> ExpenciveObjectRetrievers and override Dispose(bool) run's Dispose call of all ExpenciveObjects and call's ExpenciveObjectRetrievers, if Func returns non null, call the Dispose to it. your tt's add object to ExpenciveObjects or ExpenciveObjectRetrievers.Add( () => this.MyProperty ) Note: I know 'only one chance for implement class initializer problem' and 'only one chance for implement defalut constructor problem' , 'only one chance for implement override Initialize problem' but i dou't have utilizable solution for it. let me it. ( I will be implement 'override Initialize' in this Jail Brake kit, but this is only one chance and by case broke your tt's, I do not it, remember call Initialize method on my ToStringHelper on your file head!!)
csproj.user を作るための T4 テンプレート
2010/08/13
投稿者: : スイカドライブに BuildOptions.tt なるものを投下いたしましたのでご案内。
(またリンクを Spaces が消したら Blog の移転を考えるなう)
デバッグモードを持っていて、デバッグモード時には msbuild を実行してログを取って BuildOptions.txt に吐き出してくれますので、編集して保存(T4 では save is execute なのだよ)の繰り返しでパタパタといじっていけるはずです。デバッグモードでない場合にはこんなのを csproj.user に吐いたよと BuildOptions.txt に吐き出します。
こいつに色々と盛り込まれている 「よく訓練されたT4使いになる為のあれこれ」 をご紹介しましょう。
- よく訓練されたT4使いは 「何を元に作るか」 「何を作るか」 だけを考える。
<# Generate( (何を元に作るか), (o)=>{#> 何を作るか <#} ); #> と書く。
ファイルの最後に飛んで以下のように書く。
<#+
void Generate<T>( IEnumerable<T> seq, Action<T> action )
{
foreach( var item in seq ) { action(item) };
}
#>ポイントは T4 のコードブロック中で (value)=>{#> むにゃむにゃ <#} は立派な Action<T> な statement lambda だという事です。
何を元に作るかはきっと from … select になるでしょう。 何を作るかの中では <#=o.Property#> で値を出力する事ができます。
- よく訓練されたT4使いは TextTemplate クラスの外にコードを置ける
さて、<#@ include filename=""> でこんなファイルを取り込むとします。
<#+ }
// ここはクラスの外!
public partial class 読み込み元のTTのクラス名 { #>
1で作った Generate メソッドを IEnumerable<T> に対する拡張メソッドにするとかそんな事しちゃいけません。ダメ、絶対。
- よく訓練されたT4使いは T4 の Write を殺す
今回はやってませんけど(出力はxmlとtextだし、改行の調整とか必要無いから)、習慣としてファイル末尾で
<#+
public new void Write( string value )
{
GenerationEnvironment.Append( value.Replace( Environment.NewLine, Environment.NewLine+CurrentIndent );
}
#>するほうが良いです。
VS2010 使いの人は CustomTool を GeneratorからPreprocessor に変えると cs ファイルを得る事ができますが、 Base のWrite は色々ごちゃごちゃやっていて結果を想像するのが大変だし、なんだかなーです。 - よく訓練された T4 使いは ToStringHelper に絶望している。
3と似たりよったりですが、partial がついてなかったり、ToStringWithCulture に virtual をMSの中の人が付け忘れたせいで bool は TrueとかFalse とか出るし、null に対して "null" と文字列を出すとかもできません。なんかオブジェクトだすとクラス名だけになっちゃったり。List<T>とか出すと <#@ output extention=".il" #> がお勧めですか!って文字列になります。
- よく訓練された T4 使いは IFormatProvider にも絶望している。
System.Boolean.ToString( IFormatProvider )
解説
provider パラメータは予約されています。このパラメータは、このメソッドの実行には関与しません。
-
(Added 2010/8/15) 良く訓練された T4 使いは脱獄する
-
よく訓練された T4 使いは Preprocessor かけてから Write(ToStringHelper.ToStringWithCulture( を置換する
(もうT4じゃなくなってるという突っ込みは許可しない)
- よく訓練された T4 使いは TextTemplatingFileGenerator をレジストリ検索して…
「HOWTO: Visual Studio のカスタムツールの作り方 (C#) 」ってMSDNドキュメントとか、KBどっかに無いもんか?
まぁ、後半は冗談交じりですがこんな感じです。
VS2010 への移行がらみで CLR2 と CLR4 の両方をターゲットにビルドしたいとか色々ありげな今日この頃、皆様の助けとなる tt であるかなーとかいう事で。TTファイルは以下
( やっぱり消えた、僕の SkyDrive の public にあるからどーぞ )
以下、余談。
そうそう、CLR 4.0 (mscorlib 4.0) で string は真に imuttable になりましたねー
CLR2 の間は「string は imuttable だから」とか書いてあると鼻で笑ってしまう僕だったのですが、もうそんな事も無くなって良いことです。
え? って思ったアナタは mscorlib 2.0 の StringBuilder の Append にデバッガで step-in してみる事をお勧めします。
CLR2.0 においての StringBuilder は「String.AppendInplace へのアクセスヘルパであり、capacity overflow に対する failsafe を提供するクラス」で、String は imuttable でなく repeatly editableだったんですよ。(ΩΩΩΩ< な、なんだってー)
StringBuilder の m_StringValue が無くなった事で動かなくなったコードが(アワワワワ
ちなみに超パフォーマンススペシフィックなシチュエーションに対してよく訓練された .NET er は
public static class StringBuilderExtention
{
/// <summary>
/// ToString と同じ動作だけど、ここでコピーが発生するって事を示す為にワザトextentionする
/// </summary>
public static string CopyString( this StringBuilder sb ) { return sb.ToString(); }
}
とか、コピーを避ける為に m_StringValue を取り出して使うとかしてたりしてなかったり…