kazuk は null に触れてしまった

C# / .NET 系技術ネタ縛りでお送りしております

月別アーカイブ: 8月 2010

QueryProviderの部品いろいろ


T4からの脱獄キットリリースしました


つい先日、「良く訓練された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 テンプレート


スイカドライブに BuildOptions.tt なるものを投下いたしましたのでご案内。
(またリンクを Spaces が消したら Blog の移転を考えるなう)
 
 デバッグモードを持っていて、デバッグモード時には msbuild を実行してログを取って BuildOptions.txt に吐き出してくれますので、編集して保存(T4 では save is execute なのだよ)の繰り返しでパタパタといじっていけるはずです。デバッグモードでない場合にはこんなのを csproj.user に吐いたよと BuildOptions.txt に吐き出します。
 
こいつに色々と盛り込まれている 「よく訓練されたT4使いになる為のあれこれ」 をご紹介しましょう。
 
  1. よく訓練された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#> で値を出力する事ができます。

  2. よく訓練されたT4使いは TextTemplate クラスの外にコードを置ける

    さて、<#@ include filename=""> でこんなファイルを取り込むとします。

    <#+ } 

    // ここはクラスの外!

    public partial class 読み込み元のTTのクラス名 { #>

    1で作った Generate メソッドを IEnumerable<T> に対する拡張メソッドにするとかそんな事しちゃいけません。ダメ、絶対。

  3. よく訓練された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 は色々ごちゃごちゃやっていて結果を想像するのが大変だし、なんだかなーです。

  4. よく訓練された T4 使いは ToStringHelper に絶望している。

    3と似たりよったりですが、partial がついてなかったり、ToStringWithCulture に virtual をMSの中の人が付け忘れたせいで bool は TrueとかFalse とか出るし、null に対して "null" と文字列を出すとかもできません。なんかオブジェクトだすとクラス名だけになっちゃったり。List<T>とか出すと <#@ output extention=".il" #> がお勧めですか!って文字列になります。

  5. よく訓練された T4 使いは IFormatProvider にも絶望している。

    System.Boolean.ToString( IFormatProvider )

    解説


    provider パラメータは予約されています。このパラメータは、このメソッドの実行には関与しません。

  6. (Added 2010/8/15) 良く訓練された T4 使いは脱獄する

    脱獄キットをリリースしました

  7. よく訓練された T4 使いは Preprocessor かけてから Write(ToStringHelper.ToStringWithCulture( を置換する

    (もうT4じゃなくなってるという突っ込みは許可しない)

  8. よく訓練された 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 を取り出して使うとかしてたりしてなかったり…