この投稿の例とそのフォローアップの質問に従って、コンパイルされた式を使用してフィールドゲッター/セッターを作成しようとしています。
ゲッターはうまく機能しますが、任意のタイプのフィールドを割り当てるにはセッターが必要なので、セッターに行き詰まっています。
ここで私のセッターアクションビルダー:
public static Action<T1, T2> GetFieldSetter<T1, T2>(this FieldInfo fieldInfo) {
if (typeof(T1) != fieldInfo.DeclaringType && !typeof(T1).IsSubclassOf(fieldInfo.DeclaringType)) {
throw new ArgumentException();
}
ParameterExpression targetExp = Expression.Parameter(typeof(T1), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(T2), "value");
//
// Expression.Property can be used here as well
MemberExpression fieldExp = Expression.Field(targetExp, fieldInfo);
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp);
//
return Expression.Lambda<Action<T1, T2>> (assignExp, targetExp, valueExp).Compile();
}
ここで、一般的なセッターをキャッシュ リストに格納し (もちろん、毎回セッターを構築するとパフォーマンスが低下するため)、単純な "オブジェクト" としてキャストします。
// initialization of the setters dictionary
Dictionary<string, object> setters = new Dictionary(string, object)();
Dictionary<string, FieldInfo> fldInfos = new Dictionary(string, FieldInfo)();
FieldInfo f = this.GetType().GetField("my_int_field");
setters.Add(f.Name, GetFieldSetter<object, int>(f);
fldInfos.Add(f.Name, f);
//
f = this.GetType().GetField("my_string_field");
setters.Add(f.Name, GetFieldSetter<object, string>(f);
fldInfos.Add(f.Name, f);
今、私はこのようなフィールド値を設定しようとしています:
void setFieldValue(string fieldName, object value) {
var setterAction = setters[fieldName];
// TODO: now the problem => how do I invoke "setterAction" with
// object and fldInfos[fieldName] as parameters...?
}
ジェネリックメソッドを呼び出して毎回キャストすることもできますが、パフォーマンスのオーバーヘッドが心配です...何か提案はありますか?
--編集済みの回答アンダーソン氏の回答に
基づいて、値の直接設定、キャッシュされたリフレクション (FieldInfo がキャッシュされる場所)、およびキャッシュされたマルチタイプ コードを比較する小さなテスト プログラムを作成しました。最大 3 レベルの継承 ( ObjectC : ObjectB : ObjectA
) でオブジェクト継承を使用します。
テストを 1 回繰り返すと、次の出力が得られます。
-------------------------
--- OBJECT A ---
-------------------------
Set direct: 0.0036 ms
Set reflection: 2.319 ms
Set ref.Emit: 1.8186 ms
Set Accessor: 4.3622 ms
-------------------------
--- OBJECT B ---
-------------------------
Set direct: 0.0004 ms
Set reflection: 0.1179 ms
Set ref.Emit: 1.2197 ms
Set Accessor: 2.8819 ms
-------------------------
--- OBJECT C ---
-------------------------
Set direct: 0.0024 ms
Set reflection: 0.1106 ms
Set ref.Emit: 1.1577 ms
Set Accessor: 2.9451 ms
もちろん、これは単にオブジェクトを作成するコストを示しています。これにより、Reflection と Expressions のキャッシュされたバージョンを作成するオフセットを測定できます。
次に、1.000.000 回実行してみましょう。
-------------------------
--- OBJECT A ---
-------------------------
Set direct: 33.2744 ms
Set reflection: 1259.9551 ms
Set ref.Emit: 531.0168 ms
Set Accessor: 505.5682 ms
-------------------------
--- OBJECT B ---
-------------------------
Set direct: 38.7921 ms
Set reflection: 2584.2972 ms
Set ref.Emit: 971.773 ms
Set Accessor: 901.7656 ms
-------------------------
--- OBJECT C ---
-------------------------
Set direct: 40.3942 ms
Set reflection: 3796.3436 ms
Set ref.Emit: 1510.1819 ms
Set Accessor: 1469.4459 ms
完全を期すために、「set」メソッドの呼び出しを削除して、セッターを取得するコストを強調しました(FieldInfo
リフレクションメソッドのAction<object, object>
場合、式の場合)。結果は次のとおりです。
-------------------------
--- OBJECT A ---
-------------------------
Set direct: 3.6849 ms
Set reflection: 44.5447 ms
Set ref.Emit: 47.1925 ms
Set Accessor: 49.2954 ms
-------------------------
--- OBJECT B ---
-------------------------
Set direct: 4.1016 ms
Set reflection: 76.6444 ms
Set ref.Emit: 79.4697 ms
Set Accessor: 83.3695 ms
-------------------------
--- OBJECT C ---
-------------------------
Set direct: 4.2907 ms
Set reflection: 128.5679 ms
Set ref.Emit: 126.6639 ms
Set Accessor: 132.5919 ms
注: ここでの時間の増加は、(アクセス時間があるため) 大きな辞書ほどアクセス時間が遅くなるという事実によるものではなく、アクセス回数が増加O(1)
するという事実によるものです (1 回の反復につき 4 回ObjectA
、8 for ObjectB
, 12 for ObjectC
)... ご覧のとおり、作成オフセットのみがここで違いを生みます (これは予想されることです)。
結論: パフォーマンスは 2 倍以上向上しましたが、ダイレクト フィールド セットのパフォーマンスにはまだほど遠い状態です... リスト内の正しいセッターを取得できる確率は 10% です。
Reflection.Emit の代わりに式ツリーを試して、ギャップをさらに減らすことができるかどうかを確認します...コメントは大歓迎です。
EDIT 2この投稿でEli Arbelが 提案したように、一般的な「アクセサ」クラスを使用したアプローチを使用して結果を追加しました。