Following the examples on this post and its follow-up question, I am trying to create field getters / setters using compiled expressions.
The getter works just great, but I am stuck the setter, as I need the setter to assign any type of fields.
Here my setter-action builder:
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();
}
Now, I store the generic setters into a cache list (because of course, building the setter each time is a performance killer), where I cast them as simple "objects":
// 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);
Now I try to set a field value like this:
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...?
}
I could simply call a generic method and cast each time, but I am worried about the performance overhead... Any suggestions?
-- EDITED ANSWER
Based on Mr Anderson's answer, I created a small test program which compares directly setting the value, cached reflection (where FieldInfo's are cached) and the cached multi-type code. I use object inheritance with up to 3 level of inheritance (ObjectC : ObjectB : ObjectA
).
Full code is of the example can be found here.
Single iteration of the test gives following output:
-------------------------
--- 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
Of course, this simply shows the cost of creating the objects - this allows us to measure the offset of creating the cached versions of Reflection and Expressions.
Next, let's run 1.000.000 times:
-------------------------
--- 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
For the sake of completeness: I removed the call to the "set" method to highlight the cost of getting the setter (FieldInfo
for the reflection method, Action<object, object>
for the expression case). Here the results:
-------------------------
--- 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
NOTE: time increase here is not due to the fact that access times are slower for larger dictionaries (as they have O(1)
access times), but due to the fact that the number of times we access it is increased (4 times per iteration for ObjectA
, 8 for ObjectB
, 12 for ObjectC
)... As one sees, only the creation offset makes a difference here (which is to be expected).
Bottom line: we did improve performance by a factor of 2 or more, but we're still far away from the direct field set's performance... Retrieving the correct setter in the list represents a good 10% of the time.
I'll try with expression trees in place of Reflection.Emit to see if we can further reduce the gap... Any comment is more than welcome.
EDIT 2 I added results using the approach using a generic "Accessor" class as suggested by Eli Arbel on this post.
System.Reflection.Emit.DyanamicMethod
is much more straightforward. – Giantess