VB.NETでも式木を扱いたい(その2)
はじめに
今回も引き続き式木を扱っていきましょう。
そもそも式木とはなんでしょう。 という事でmsdnさんオナシャス
アレでは上記の
- コードをツリー状のデータ構造で表現できる
- 実行時に動的に生成できる
- そして実行できる
- わりと速い
- そして実行できる
- 実行時に動的に生成できる
- コードをデータ構造として解析する事ができる
というのを利用しています。
動的に実行するだけなら従来のリフレクションでもできますが、式木をコンパイルして実行した場合は倍近く速い(当社比)のでそれだけでも式木を使う理由になると思います。
プロパティ/フィールドの取得
アレはざっくり言うと以下の流れで処理を行っています。
- リフレクションを使ってクラスからプロパティ・フィールドを
MemberInfo
としてぶっこぬく - 1の
MemberInfo
を用いてそのクラスのインスタンスからそのメンバーを取得する式を式木で構築する - 1、2をすべてのプロパティ・フィールドに対して行い、キャッシュする
- 3でキャッシュした式を用いてインスタンスからプロパティ・フィールドの値を取得し文字列形式を作成する
取り敢えず1はリフレクションなのでパスし、3は別問題なのでパスします。
と、言うわけで2からやっていきましょう。
前回に引き続きHoge
クラスさんにご登場願いましょう。
Class Hoge Public Property MyProperty As Integer Public Property MyProperty2 As String Public MyField As String End Class
このHoge
からMyProperty
の値を取得する式木を構築して、実際に取得してみましょう。
紹介したmsdnのページにも書いてあったので気付いた方もいると思いますが、ラムダ式をExpression(Of TDelegate)
に突っ込めばコンパイラが式木を構築してくれるのでまずはそれを参考にしましょう。
Dim expr As Expression(Of Func(Of Object, Object)) = Function(it) CType(it, Hoge).MyProperty
実際はコンパイル結果を逆コンパイルしたりクイックウォッチなどで中身を参考にしながら改変して目的のものを作るのですが、今回はこちらに完成済みのものを用意しました。
式木を構築するにはSystem.Linq.Expressions
名前空間のExpression
クラスの各静的メソッドを用います。
アレと同じにするならMemberInfo
から式木を組み立てるのですが、わざわざ単体でMemberInfo
を取り出すのも面倒なのでここではプロパティを文字列で指定してます。
Dim arg = Expression.Parameter(GetType(Object), "it") Dim convertToHoge = Expression.Convert(arg, GetType(Hoge)) Dim getMemberValue = Expression.Property(convertToHoge, "MyProperty") Dim convertToObject = Expression.Convert(getMemberValue, GetType(Object)) Dim lambda = Expression.Lambda(Of Func(Of Object, Object))(convertToObject, arg) Dim expr = lambda.Compile() Dim h = New Hoge() With {.MyProperty = 10} Console.WriteLine(expr(h))
一行ずつ見てみましょう。
Dim arg = Expression.Parameter(GetType(Object), "it")
はFunction(it)
のit
を表します。
Dim convertToHoge = Expression.Convert(arg, GetType(Hoge))
はCType(it, Hoge)
を表します。
Dim getMemberValue = Expression.Property(convertToHoge, "MyProperty")
はCType(it, Hoge).MyProperty
の.MyProperty
を表します。
Dim convertToObject = Expression.Convert(getMemberValue, GetType(Object))
はラムダ式に明示的に書かれていませんが、CType(CType(it, Hoge).MyProperty, Object)
のCType(◯◯, Object)
を表します。
最後のDim lambda = Expression.Lambda(Of Func(Of Object, Object))(convertToObject, arg)
でFunc(Of Object, Object)
型のデリゲートを表現しているFunction(◯◯) ◯◯
を表しています。(言い方が変かな?)
これでHoge.MyProperty
にアクセスするコードを表現する式木の完成です。お疲れさま。
最後にコンパイルすれば晴れて実行可能なデリゲートが出来上がります。
Dim expr = lambda.Compile() Dim h = New Hoge() With {.MyProperty = 10} Console.WriteLine(expr(h))
これを繰り返してクラスのすべてのパブリックプロパティ・フィールドにアクセスするデリゲートを作成します。
ちなみになんでFunc(Of Object, Object)
なのかと言うと、ToString
するだけなら別にObject
型でも構わないからなるべく手間をかけたくないというちょっと乱暴な理由があります。
おわりに
今回は単一のプロパティの値を取得する式木について説明しました。
正直あとはリフレクションで取得したメンバーをフィルタしつつ同様に他のプロパティ・フィールドに対してもアクセッサを作るだけなので周りのごたごたが追加されるだけなのですが、次回はその辺の紹介をしたいと思います。
ちなみにアレではなんでプロパティだけでなくフィールドもサポートしているのかと言いますと、固定長ファイルフォーマットのマッパーライブラリにFileHelpersというものがあるのですがマッピング対象がパブリックフィールドという割とアバンギャルドな仕様になっておりまして、それに対応するために泣く泣くフィールドにも対応させたと言った感じなのです。*1 ですので、もともとはプロパティのみのサポートとするつもりだったのです。
とは言え、自前でマッピングするよりはずっと楽なので未だに固定長ファイルフォーマットと格闘しなければならない生産性アプリケーション開発者にはオススメです。まぁ、日本語の解説がほぼ無いのが欠点なのですが。
つづく