リフレクションは遅いから情報をキャッシュするとかのお話

フレームワーク層ではリフレクションとかよく使うけど、リフレクションは遅いと言う話がよく出ますが。
っで、ちょっとプロパティのキャッシュをやってみたんですが、こんなんで良いのかしら(・ω・)?

// Getter/Setter
public interface IAccessor
{
    object GetValue(object target);

    void SetValue(object target, object value);
}

// IAccessorの実装
internal sealed class Accessor<TTarget, TProperty> : IAccessor
{
    private readonly Func<TTarget, TProperty> getter;
    private readonly Action<TTarget, TProperty> setter;

    public Accessor(Func<TTarget, TProperty> getter, Action<TTarget, TProperty> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }

    public object GetValue(object target)
    {
        return this.getter( (TTarget)target ); ;
    }

    public void SetValue(object target, object value)
    {
        this.setter( (TTarget)target, (TProperty)value );
    }
}

// PropertyInfoからIAccessorへの変換
public static class PropertyExtension
{
    public static IAccessor ToAccessor(this PropertyInfo pi)
    {
        Type getterDelegateType = typeof(Func<,>).MakeGenericType( pi.DeclaringType, pi.PropertyType );
        Delegate getter = Delegate.CreateDelegate( getterDelegateType, pi.GetGetMethod() );

        Type setterDelegateType = typeof(Action<,>).MakeGenericType( pi.DeclaringType, pi.PropertyType );
        Delegate setter = Delegate.CreateDelegate( setterDelegateType, pi.GetSetMethod() );

        Type accessorType = typeof(Accessor<,>).MakeGenericType( pi.DeclaringType, pi.PropertyType );
        IAccessor accessor = (IAccessor)Activator.CreateInstance( accessorType, getter, setter );

        return accessor;
    }
}

PrpertyInfoをキャッシュしてもそろほど速くならないので、Delegateにした形(IAccessor)でキャッシュするわけですね(・∀・)
環境やテストコードにもよるんだけど、ちょっと手元で確認した値だと、[プロパティ直アクセス:Delegateキャッシュ:PrpertyInfoキャッシュ:毎回PrpertyInfo取得]での値取得の速度比は[1:17:670:1400]でスタ。


実は、今までキャッシュってしたこと無いのよね(´д`)
正確に言うと、PropertyInfoをキャッシュするDescripter的なクラスを作ったりはしているけど、それはパフォーマンス目的ではなくて、利便性が目的であって。
今までキャッシュしたことの無い理由は、下記によるものですが。

  • DBアクセスとかの処理に比べれば十分に速い(3桁とか)
  • それでも回数が気になるような時は、リフレクションではなく別戦略を考えるから

PropertyInfoのキャッシュとDelegateキャッシュを比較しても、2桁は違わないわけで。
その差を大きいと見るか小さいと見るかはやってる事によるという話ですが、まあ速い分には困ることも無いので、今までPropertyInfoをキャッシュしていたような所には代わりに使っていきましょうか(・∀・)


ちなみに、これだとプロパティにしか対応していないので、フィールドの値を直接見る時はILGeneratorでちょっとした呼び出しコードを作成してあげる必要がありますね。
こんなイメージ(・ω・)?

// Field Getter
ILGenerator il = dm.GetILGenerator();
il.Emit( OpCodes.Ldarg_0 );
il.Emit( OpCodes.Ldfld, fi );
il.Emit( OpCodes.Ret );

まあ、System.Reflection.Emitを使いだすとCompact Frameworkに対応できないんだけど(´・ω・`)


ちなみに、PropertyInfoのみの対応でCompact Frameworkに対応させる場合は、Delegate.CreateDelegate()をCreateDelegate(Type type, object firstArgument, MethodInfo method)のシグネチャのものに変更して第2引数にはnullを指定して、Activator.CreateInstance()にパラメータ付きのものが無い点については下記の様な代替を使えばOKっすね(・∀・)

public static class ActivatorEx
{
    // エラーチェック省略
    public static object CreateInstance(Type type, params object[] args)
    {
        Type[] types = new Type[ args.Length ];
        for( int i = 0; i < args.Length; i++ )
        {
            types[ i ] = args[ i ].GetType();
        }
    
        ConstructorInfo info = type.GetConstructor( ( BindingFlags.CreateInstance|BindingFlags.Public|BindingFlags.Instance|BindingFlags.Default ), null, types, null );
    
        return info.Invoke( args );
    }
}