奇想曲 in C#

2010-08-09

静的リフレクションとは?

静的リフレクション(Static Reflection)とは、やや新しい実装テクニックのひとつであり、型やメンバを指示する情報の内でコンパイラが識別可能なもののみを用いて型やメンバの情報にアクセスする方法である。通常は指示情報としてメンバアクセスを行うラムダ式が用いられるので、「ラムダ式を用いてメンバの型情報にアクセスする方法」と理解しておく方が実用的かもしれない。


リフレクションにおける静的・動的の区別をはっきりさせるために簡単な例から始めることにする。

interface X
{
    string MyProperty { get; set; }
}

class Y : X
{
    public string MyProperty { get; set; }
}
・・・
X p = new Y();
Type t1 = p.GetType();
Type t2 = typeof(X);

とある場合に、p.GetType()で得られる型情報は、実行時にpに具体的に何が代入されているかに依存しているという意味で動的である。

これに対しtypeof(X)にはそのような動的側面はなく、静的ということができる。


「動的」という言葉の含む範囲はこれだけではない。一歩進んで、クラス情報ではなくその中の"MyProperty"という名称のプロパティの情報を得たい場合はどうであろうか。

PropertyInfo pi1 = p.GetType().GetProperty("MyProperty");
PropertyInfo pi2 = typeof(X).GetProperty("MyProperty");

クラスの型情報からリフレクション機能のGetProperty()を呼び出せばよいわけだが、この時の引数が問題である。Stringでプロパティ名を渡さなければならない。この時点で、先ほどの延長である2つの文のうち後者も静的ではなくなっていることに注目する必要がある。なぜならGetPropertyが型情報にアクセスするメカニズムは、つまるところ実行時にスタックから"MyProperty"という文字列ポインタを取って、それに関して何か処理を行うという実行時処理によるものあって、コンパイラはそのことについて何も気にかけず、Stringであることをチェックする以上のことは行わないからである。(Compiler-awareでないともいう。)

もし2番目の例が次のように書けるなら、これは静的であると言える。

PropertyInfo pi2 = propertyof(X.MyProperty);

そして、これこそがまさに静的リフレクションが行いたいことなのである。


静的リフレクションの核となるアイディアは、「型情報が欲しい対象である要素を含むような任意のラムダ式作り、それを式ツリーに変換した上で解析することにより、要素の型情報を得る」というものである。

ラムダ式は何であってもよいので、解析が楽になるようできるだけ簡単なものとする。例えば、Propertyの情報がほしいのであれば、それを含むクラスのインスタンス(c)を引数に取って、そのProperty値(MyProperty)を返す式(c => c.MyProperty)を用いればよい。Expression(式ツリー)の形で得たのが次のコードである。

var exp = (Expression<Func<X, object>>)(c => c.MyProperty);

これを式ツリーの機能を用いて解析するのであるが、具体的には右辺(Body)の表現するMember式からMember情報を取るだけで済む。

PropertyInfo pi3 = ((MemberExpression)exp.Body).Member as PropertyInfo;

型情報を指示するラムダ式から目的の型情報PropertyInfoを得る所まで、実行時の情報が含まれていないことがわかる(キャストは本筋とは無関係なので除外して考える)。

なお、ラムダ式は式ツリーに変換されるだけであり、評価(Invoke)すらなされない。しかし、コンパイラが解釈することのできるメンバアクセスの「形」を与えてくれているのである。


以上が原理であるが、より使いやすくするには以上の操作をジェネリックメソッドGetProperty<T>()の形に一般化ておくとよい。

public static PropertyInfo GetProperty<T>(Expression<Func<T, object>> exp)
{
    return ((MemberExpression)exp.Body).Member as PropertyInfo;
}


このように定義すれば、次のようにしてGetPropertyを使用することができる。

PropertyInfo pi = GetProperty<X>(c => c.MyProperty);


MethodInfoの場合はメソッドのパラメータ数に応じたDelegateが必要になるが、元々この手法の用途がSerializationなどメンバ情報に対する操作の実現であることが多いせいかあまり問題となっていないようである。


Serializationに用いるとはいうものの、XMLSerializerのように、あるオブジェクトのメンバのうちいくつか指定したメンバについてリフレクション情報を得るというのであれば、Type.Properties, Type.GetMembers()と属性(Attribute)を組み合わせることで、同等のことが可能である。しかし、その場合Attributeによって対象オブジェクトの定義を侵すことになり、メンバ操作の指定をオブジェクト定義から分離できない。

この手法の利点は何といっても文字列でアクセスしなくて済むので、Visual StudioのIntelliSenseやリファクタリング機能がフルに適用できる点であろう。

逆に欠点は、通常の動的リフレクションよりもメモリ消費と速度の点で劣ることである。


※静的リフレクションの使用例:

・FluentNHibernate: http://fluentnhibernate.org/

(src/FluentNHibernate/Utils/Reflection/ReflectionHelper.csを参照)

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

Connection: close