Let\'s say I have the following code which update a field of a struct
using reflection. Since the struct instance is copied into the DynamicUpdate
I ran into a similar issue, and it took me most of a weekend, but I finally figured it out after a lot of searching, reading, and disassembling C# test projects. And this version only requires .NET 2, not 4.
public delegate void SetterDelegate(ref object target, object value);
private static Type[] ParamTypes = new Type[]
{
typeof(object).MakeByRefType(), typeof(object)
};
private static SetterDelegate CreateSetMethod(MemberInfo memberInfo)
{
Type ParamType;
if (memberInfo is PropertyInfo)
ParamType = ((PropertyInfo)memberInfo).PropertyType;
else if (memberInfo is FieldInfo)
ParamType = ((FieldInfo)memberInfo).FieldType;
else
throw new Exception("Can only create set methods for properties and fields.");
DynamicMethod setter = new DynamicMethod(
"",
typeof(void),
ParamTypes,
memberInfo.ReflectedType.Module,
true);
ILGenerator generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldind_Ref);
if (memberInfo.DeclaringType.IsValueType)
{
#if UNSAFE_IL
generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType);
#else
generator.DeclareLocal(memberInfo.DeclaringType.MakeByRefType());
generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType);
generator.Emit(OpCodes.Stloc_0);
generator.Emit(OpCodes.Ldloc_0);
#endif // UNSAFE_IL
}
generator.Emit(OpCodes.Ldarg_1);
if (ParamType.IsValueType)
generator.Emit(OpCodes.Unbox_Any, ParamType);
if (memberInfo is PropertyInfo)
generator.Emit(OpCodes.Callvirt, ((PropertyInfo)memberInfo).GetSetMethod());
else if (memberInfo is FieldInfo)
generator.Emit(OpCodes.Stfld, (FieldInfo)memberInfo);
if (memberInfo.DeclaringType.IsValueType)
{
#if !UNSAFE_IL
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldloc_0);
generator.Emit(OpCodes.Ldobj, memberInfo.DeclaringType);
generator.Emit(OpCodes.Box, memberInfo.DeclaringType);
generator.Emit(OpCodes.Stind_Ref);
#endif // UNSAFE_IL
}
generator.Emit(OpCodes.Ret);
return (SetterDelegate)setter.CreateDelegate(typeof(SetterDelegate));
}
Note the "#if UNSAFE_IL" stuff in there. I actually came up with 2 ways to do it, but the first one is really... hackish. To quote from Ecma-335, the standards document for IL:
"Unlike box, which is required to make a copy of a value type for use in the object, unbox is not required to copy the value type from the object. Typically it simply computes the address of the value type that is already present inside of the boxed object."
So if you want to play dangerously, you can use OpCodes.Unbox to change your object handle into a pointer to your structure, which can then be used as the first parameter of a Stfld or Callvirt. Doing it this way actually ends up modifying the struct in place, and you don't even need to pass your target object by ref.
However, note that the standard doesn't guarantee that Unbox will give you a pointer to the boxed version. Particularly, it suggests that Nullable<> can cause Unbox to create a copy. Anyway, if that happens, you'll probably get a silent failure, where it sets the field or property value on a local copy which is then immediately discarded.
So the safe way to do it is pass your object by ref, store the address in a local variable, make the modification, and then rebox the result and put it back in your ByRef object parameter.
I did some rough timings, calling each version 10,000,000 times, with 2 different structures:
Structure with 1 field: .46 s "Unsafe" delegate .70 s "Safe" delegate 4.5 s FieldInfo.SetValue
Structure with 4 fields: .46 s "Unsafe" delegate .88 s "Safe" delegate 4.5 s FieldInfo.SetValue
Notice that the boxing makes the the "Safe" version speed decrease with structure size, whereas the other two methods are unaffected by structure size. I guess at some point the boxing cost would overrun the reflection cost. But I wouldn't trust the "Unsafe" version in any important capacity.