按照这篇文章的示例及其后续问题,我正在尝试使用已编译的表达式创建字段获取器/设置器。
getter 工作得很好,但我被 setter 卡住了,因为我需要 setter 来分配任何类型的字段。
这是我的 setter-action 构建器:
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
) 的对象继承。
测试的单次迭代给出以下输出:
-------------------------
--- 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
当然,这只是显示了创建对象的成本——这使我们能够测量创建反射和表达式的缓存版本的偏移量。
接下来,让我们运行 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”方法的调用,以突出获取 setter 的成本(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)
访问时间),而是因为我们访问它的次数增加了(每次迭代 4 次ObjectA
,8 for ObjectB
, 12 for ObjectC
)... 正如我们所看到的,这里只有创建偏移量会有所不同(这是意料之中的)。
底线:我们确实将性能提高了 2 倍或更多,但距离直接字段集的性能还很远……在列表中检索正确的 setter 代表了 10% 的好时机。
我会尝试用表达式树代替 Reflection.Emit 看看我们是否可以进一步缩小差距......任何评论都非常受欢迎。
编辑 2我使用Eli Arbel在这篇文章 中建议的使用通用“访问器”类的方法添加了结果。