Getter / setter field with expression tree in base class

Following the examples of this post and its subsequent question , I am trying to create a getters / seters field using compiled expressions.

The getter works just fine, but I'm stuck with the setter, as I need the installer to assign any type of field.

Here is my action constructor:

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 universal setters in the cache list (because, of course, creating a setter every time is a performance killer), where I drop 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 am trying to set the field value as follows:

  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 just call the generic method and drop it every time, but I'm worried about performance overload ... Any suggestions?

- SEPARATE RESPONSE Based on Mr Anderson's answer , I created a small test program that directly compares the value setting, cached reflection (where FieldInfo caching is cached) and cached multi-type code. I use object inheritance up to 3 levels of inheritance ( ObjectC : ObjectB : ObjectA ).

The full example code can be found here.

A single iteration of the test gives the following result:

 ------------------------- --- 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 just shows the cost of creating objects - this allows us to measure the offset of creating cached versions of Reflection and Exions.

Then 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 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 case of expression). Here are 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. The increase in time here is not due to the fact that the access time is slower for large dictionaries (since they have an access time of O(1) ), but because the number of calls to it increases (4 times per iteration for ObjectA , 8 for ObjectB , 12 for ObjectC ) ... As you can see, only the creation offset here matters (which is to be expected).

On the bottom line: we have improved the performance by 2 times or more, but we are still far from the performance of the direct field ... Getting the correct setter in the list is 10% of the time.

I will try to use expression trees instead of Reflection.Emit to see if we can further reduce the space ... Any comments are more than welcome.

EDIT 2 I added the results using the approach using the general "Accessor" class proposed by Eli Arbel on this post .

+8
reflection c # setter expression-trees
source share
1 answer

If you want this to support operations on several types, your function cache should be indexed using Type AND the field name ( string ), and functions should be created lazily. Try the following:

 private static Dictionary<Type, Dictionary<string, Action<object, object>>> _typeMapper = new Dictionary<Type, Dictionary<string, Action<object, object>>>(); public static void Set(object obj, string fieldName, object newValue) { if (obj == null) { throw new ArgumentNullException("obj"); } Type type = obj.GetType(); Dictionary<string, Action<object, object>> fieldMapper; Action<object, object> action; if (_typeMapper.TryGetValue(type, out fieldMapper)) { // entry has been created for this type. if (!fieldMapper.TryGetValue(fieldName, out action)) { // method has not been created yet, must build it. FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fld == null) { throw new ArgumentException("No field " + fieldName); } action = buildSetter(fld); fieldMapper.Add(fieldName, action); // add it to method cache for future use. } } else { // -- ADDED CODE: forgot to create the new fieldMapper..... fieldMapper = new Dictionary<string, Action<object, object>>(); // type has not been added yet, so we know method has not been built yet either. FieldInfo fld = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fld == null) { throw new ArgumentException("No field " + fieldName); } action = buildSetter(fld); fieldMapper.Add(fieldName, action); // add it to method cache for future use. _typeMapper.Add(type, fieldMapper); // add it to type cache for future use. } action(obj, newValue); // invoke the method. } // this is my preferred setter-builder, feel free to use expressions instead. private static Action<object, object> buildSetter(FieldInfo fld) { DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(object), typeof(object) }, fld.DeclaringType); ILGenerator gen = dyn.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Castclass, fld.DeclaringType); gen.Emit(OpCodes.Ldarg_1); if (fld.FieldType.IsClass) { gen.Emit(OpCodes.Castclass, fld.FieldType); } else { gen.Emit(OpCodes.Unbox_Any, fld.FieldType); } gen.Emit(OpCodes.Stfld, fld); gen.Emit(OpCodes.Ret); return (Action<object, object>)dyn.CreateDelegate(typeof(Action<object, object>)); } 

Otherwise, if you need to do this with only one type, your process will look like this:

 private static Dictionary<string, Action<MyType, object>> _mapper = new Dictionary<string, Action<MyType, object>>(); public static void Set(MyType obj, string fieldName, object newValue) { if (obj == null) { throw new ArgumentNullException("obj"); } Action<MyType, object> action; if (!_mapper.TryGetValue(fieldName, out action)) { FieldInfo fld = typeof(MyType).GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (fld == null) { throw new ArgumentException("No field " + fieldName); } action = buildSetter(fld); _mapper.Add(fieldName, action); } action(obj, newValue); // invoke the method. } private static Action<MyType, object> buildSetter(FieldInfo fld) { DynamicMethod dyn = new DynamicMethod("set_" + fld, typeof(void), new[] { typeof(MyType), typeof(object) }, typeof(MyType)); ILGenerator gen = dyn.GetILGenerator(); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldarg_1); if (fld.FieldType.IsClass) { gen.Emit(OpCodes.Castclass, fld.FieldType); } else { gen.Emit(OpCodes.Unbox_Any, fld.FieldType); } gen.Emit(OpCodes.Stfld, fld); gen.Emit(OpCodes.Ret); return (Action<MyType, object>)dyn.CreateDelegate(typeof(Action<MyType, object>)); } 
+1
source share

All Articles