3

未知の型を処理できる関数のラムダ式をどのように作成しますか? 申し訳ありませんが、質問があいまいであることは承知しており、作成するのに苦労しました。少しお時間をいただき、私の話を読んでいただければ幸いです。

私の目標は、定義済みのデータ コントラクトを使用して、文字列値の配列をオブジェクトに逆シリアル化することです。データ コントラクトのメンバーには位置番号があります。デシリアライザーの単純な仕事は、(適切な型変換を行った後に) 値をデータ メンバーにマップし、オブジェクトを構築することです。

問題は、デシリアライゼーションのパフォーマンスが悪いことです! VS プロファイラーを実行した後、オブジェクトのメンバーを設定するために使用される PropertyInfo.SetValue() に最も時間がかかっていることがわかりました。私のプログラムは、いつでも何千ものオブジェクトを逆シリアル化する必要があります。通常、データ コントラクトには 100 のメンバーが含まれます。つまり、1000 個のオブジェクトごとに 100,000 回の SetValue() 呼び出しがあり、それがドラッグされています。SetValue の呼び出しのサンプルを次に示します。

// for each data contract type
// go through each property and set the value
foreach(PropertyInfo pi in pis)
{
    object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
    pi.SetValue(objectToBuild, data, null);
}

次に、このパフォーマンスの問題に対する有望な解決策があるUnknown Recipes からこのページを見つけました。SetValue を置き換えるには、コンパイル済みのラムダ式を使用する必要があるようですが、キャストで問題が発生しています。上記のリンクの例に従って、SetValue() を置き換えることができました。代替は、コンパイルされたラムダ式である Action デリゲートです。

まず、PropertyInfo クラスを拡張しました。

public static class PropertyInfoExtensions
{

    public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
    {
        var instance = Expression.Parameter(propertyInfo.DeclaringType, "i");
        var argument = Expression.Parameter(typeof(object), "a");
        var setterCall = Expression.Call(
            instance,
            propertyInfo.GetSetMethod(),
            Expression.Convert(argument, propertyInfo.PropertyType));
        return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
    }
}

次にDictionary<PropertyInfo, Action<object, object>、各 propertyInfo オブジェクトを対応する Action デリゲートに結び付けるオブジェクトを作成しました。このようにして、コンパイルされたラムダを「キャッシュ」し、逆シリアル化のバッチで再利用できます。そして、これは私が今それを呼ぶ方法です:

foreach(PropertyInfo pi in pis)
{
    object data = convertStringToMemberType(pi, attributeArray, valueStringArray);
    var setValueDelegate = _actionDelegateDict[pi];
    setValueDelegate(objectToBuild, data);
}

ただし、次の例外が発生します。

Unable to cast object of type 'System.Action`2[Test.DataContract1,System.Object]' to type 'System.Action`2[System.Object,System.Object]'.

ここで DataContract1 は、構築しようとしているオブジェクトの型です。これは、型がコンパイル時に既知である Unknown Recipes の例のシナリオとは異なります。このラムダ式を機能させるにはどうすればよいでしょうか?

お時間をいただき、誠にありがとうございました!

4

2 に答える 2

4

sを使用せずに、このラムダを直接作成していると想像してくださいExpression。それはどのように見えるでしょうか?作成したいAction<object, object>:

Action<object, object> action = (object i, object a) => i.Property = a;

しかし、これはうまくいきません。 と の両方をキャストする必要がありiますa。そう:

Action<object, object> action =
    (object i, object a) => ((DataContract)i).Property = (PropertyType)a;

コードでは、 をaキャストしていますが、キャストする必要iもあります。

public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
{
    var instance = Expression.Parameter(typeof(object), "i");
    var argument = Expression.Parameter(typeof(object), "a");
    var setterCall = Expression.Call(
        Expression.Convert(instance, propertyInfo.DeclaringType),
        propertyInfo.GetSetMethod(),
        Expression.Convert(argument, propertyInfo.PropertyType));
    return (Action<object, object>)Expression.Lambda(setterCall, instance, argument).Compile();
}
于 2012-05-03T00:46:04.823 に答える
4

私がFastReflectionライブラリで行ったこととよく似ています。インスタンス パラメータをオブジェクト型に変更し、その式を実際の型にキャストするだけです。

あなたが今持っているコードは、これに変更されればうまくいくと思います。

public static class PropertyInfoExtensions
{
    public static Action<object, object> GetValueSetter(this PropertyInfo propertyInfo)
    {
        var instance = Expression.Parameter(typeof(object), "i");
        var argument = Expression.Parameter(typeof(object), "a");
        var setterCall = Expression.Call(
            Expression.Convert(instance, propertyInfo.DeclaringType),
            propertyInfo.GetSetMethod(),
            Expression.Convert(argument, propertyInfo.PropertyType));
        return Expression.Lambda<Action<object,object>>(setterCall, instance, argument).Compile();
    }
}
于 2012-05-03T00:43:08.553 に答える