VB.NETの「魔法」を解き明かす:式ツリー(Expression Tree)で爆速オブジェクトマッパーを創る
こんにちは。VB.NETという言語の深淵に触れようとしている皆さん、ようこそ。
多くの人がVB.NETで「データのコピー」を行う際、`Reflection(リフレクション)`を使います。`GetProperty`を呼び出し、`GetValue`と`SetValue`を繰り返す……確かに便利ですが、リフレクションは「非常に高コスト」です。実行のたびにメタデータを検索するため、大量のデータを扱うシステムでは、これがボトルネックになります。
今日は、伝説的なアーキテクトが現場で使う「Expression Tree(式ツリー)」を用いた超高速オブジェクトマッパーの秘伝を伝授します。
—
1. なぜ「リフレクション」ではいけないのか?
リフレクションは、プログラムが「自分自身を覗き見る」機能です。とても強力ですが、例えるなら「毎回、辞書の索引を引いてから言葉の意味を調べる」ようなもの。
今回紹介する「式ツリー」による手法は、「最初に辞書を読み込み、必要な単語を脳内に直接焼き付ける(コンパイルする)」ようなものです。一度焼き付けてしまえば、実行速度はネイティブコードと同等になります。
—
2. 式ツリー(Expression Tree)とは何か?
式ツリーとは、「コードそのものをデータとして扱う仕組み」です。
通常、VB.NETのコードはコンパイルされてマシン語になります。しかし、式ツリーを使えば、プログラムの実行中に「プロパティAの値をプロパティBに代入する」という命令を、ツリー状のデータ構造として組み立て、それをその場でコンパイルして実行できるのです。
図解:処理の違い
- リフレクション: 実行毎に「名前」からプロパティを探し、値を転送(遅い)
- 式ツリー: 最初に「代入処理」という命令を組み立て、メモリ上に最適化された関数として常駐させる(爆速)
—
3. 実装:高速オブジェクトマッパーの構築
それでは、実際に動的なマッパーを実装してみましょう。
.net
Imports System.Linq.Expressions
Imports System.Reflection
Public Class FastMapper(Of TSource, TTarget)
‘ コンパイル済みの関数を保持するキャッシュ
Private Shared ReadOnly _mapper As Func(Of TSource, TTarget)
Shared Sub New()
‘ 1. 引数(Source)のパラメータ式を作成
Dim sourceParam = Expression.Parameter(GetType(TSource), “src”)
‘ 2. ターゲットインスタンスの生成
Dim targetNew = Expression.New(GetType(TTarget))
‘ 3. プロパティの代入処理を作成
Dim bindings = New List(Of MemberBinding)()
For Each prop In GetType(TSource).GetProperties()
Dim targetProp = GetType(TTarget).GetProperty(prop.Name)
If targetProp IsNot Nothing AndAlso targetProp.CanWrite Then
‘ src.Property を取得し、それを target.Property に代入するバインディング
Dim propertyAccess = Expression.Property(sourceParam, prop)
bindings.Add(Expression.Bind(targetProp, propertyAccess))
End If
Next
‘ 4. MemberInit(New TTarget { … }) という式を構築
Dim body = Expression.MemberInit(targetNew, bindings)
‘ 5. ラムダ式としてコンパイルしてキャッシュ
_mapper = Expression.Lambda(Of Func(Of TSource, TTarget))(body, sourceParam).Compile()
End Sub
Public Shared Function Map(source As TSource) As TTarget
Return _mapper(source)
End Function
End Class
コードの解説
1. Shared Sub New(静的コンストラクタ): クラスが最初に呼ばれた時だけ実行されます。ここで一度だけ「式ツリーの構築とコンパイル」を行います。
2. Expression.Parameter: ラムダ式の引数 `(src) => …` の `src` を定義しています。
3. Expression.Bind: 「プロパティAをプロパティBに代入する」という命令を作成します。
4. Compile(): これが魔法のメソッドです。構築したツリーをIL(中間言語)に変換し、メモリ上に「関数」として生成します。
—
4. 陥りやすいエラーと回避策
初学者がこの領域で躓きやすいポイントは以下の2つです。
- 型不一致: ソースとターゲットでプロパティの型が違うと例外になります。実戦では `Expression.Convert` を挟んで明示的にキャストを行う必要があります。
- コンパイルコスト: 構築とコンパイル自体は重い処理です。必ず `Shared` な変数や静的コンストラクタでキャッシュし、二度と同じ処理が走らないようにしましょう。
—
最後に:なぜVB.NETでやるのか?
「C#の方が式ツリーの構文が綺麗では?」と思うかもしれません。確かに構文上の簡潔さはC#に分があります。しかし、VB.NETには「XMLリテラル」や「強力な動的バインディング機能」があり、ビジネスロジックの可読性においては今なお最強の言語の一つです。
式ツリーを掌握すれば、皆さんはもう「VB.NETで書かれた古いシステム」を「現代の最速エンジン」へと進化させることができます。
ここをクリアしたあなたは、もう初心者ではありません。次は、このキャッシュされた関数をさらに拡張して、ネストされたオブジェクトのコピーにも対応させてみてください。エンジニアとしての視界が、確実に変わるはずです。
応援していますよ!
