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
method, it needs to be boxed to an object before being passed.
struct Person
{
public int id;
}
class Test
{
static void Main()
{
object person = RuntimeHelpers.GetObjectValue(new Person());
DynamicUpdate(person);
Console.WriteLine(((Person)person).id); // print 10
}
private static void DynamicUpdate(object o)
{
FieldInfo field = typeof(Person).GetField("id");
field.SetValue(o, 10);
}
}
The code works fine. Now, let's say I don't want to use reflection because it's slow. Instead, I want to generate some CIL directly modifying the id
field and convert that CIL into a reusable delegate (say, using Dynamic Method feature). Specially, I want to replace the above code with s/t like this:
static void Main()
{
var action = CreateSetIdDelegate(typeof(Person));
object person = RuntimeHelpers.GetObjectValue(new Person());
action(person, 10);
Console.WriteLine(((Person)person).id); // print 10
}
private static Action<object, object> CreateSetIdDelegate(Type t)
{
// build dynamic method and return delegate
}
My question: is there any way to implement CreateSetIdDelegate
excepts from using one of the following techniques?
Action<object, object>
, use a custom delegate whose signature is public delegate void Setter(ref object target, object value)
.Action<object, object>
, use Action<object[], object>
with the 1st element of the array being the target object. The reason I don't like 2 & 3 is because I don't want to have different delegates for the setter of object and setter of struct (as well as not wanting to make the set-object-field delegate more complicated than necessary, e.g. Action<object, object>
). I reckon that the implementation of CreateSetIdDelegate
would generate different CIL depending whether the target type is struct or object, but I want it to return the same delegate offering the same API to user.
This code works for structs without using ref:
private Action<object, object> CreateSetter(FieldInfo field)
{
var instance = Expression.Parameter(typeof(object));
var value = Expression.Parameter(typeof(object));
var body =
Expression.Block(typeof(void),
Expression.Assign(
Expression.Field(
Expression.Unbox(instance, field.DeclaringType),
field),
Expression.Convert(value, field.FieldType)));
return (Action<object, object>)Expression.Lambda(body, instance, value).Compile();
}
Here is my test code:
public struct MockStruct
{
public int[] Values;
}
[TestMethod]
public void MyTestMethod()
{
var field = typeof(MockStruct).GetField(nameof(MockStruct.Values));
var setter = CreateSetter(field);
object mock = new MockStruct(); //note the boxing here.
setter(mock, new[] { 1, 2, 3 });
var result = ((MockStruct)mock).Values;
Assert.IsNotNull(result);
Assert.IsTrue(new[] { 1, 2, 3 }.SequenceEqual(result));
}
After some experiments:
public delegate void ClassFieldSetter<in T, in TValue>(T target, TValue value) where T : class;
public delegate void StructFieldSetter<T, in TValue>(ref T target, TValue value) where T : struct;
public static class FieldSetterCreator
{
public static ClassFieldSetter<T, TValue> CreateClassFieldSetter<T, TValue>(FieldInfo field)
where T : class
{
return CreateSetter<T, TValue, ClassFieldSetter<T, TValue>>(field);
}
public static StructFieldSetter<T, TValue> CreateStructFieldSetter<T, TValue>(FieldInfo field)
where T : struct
{
return CreateSetter<T, TValue, StructFieldSetter<T, TValue>>(field);
}
private static TDelegate CreateSetter<T, TValue, TDelegate>(FieldInfo field)
{
return (TDelegate)(object)CreateSetter(field, typeof(T), typeof(TValue), typeof(TDelegate));
}
private static Delegate CreateSetter(FieldInfo field, Type instanceType, Type valueType, Type delegateType)
{
if (!field.DeclaringType.IsAssignableFrom(instanceType))
throw new ArgumentException("The field is declared it different type");
if (!field.FieldType.IsAssignableFrom(valueType))
throw new ArgumentException("The field type is not assignable from the value");
var paramType = instanceType.IsValueType ? instanceType.MakeByRefType() : instanceType;
var setter = new DynamicMethod("", typeof(void),
new[] { paramType, valueType },
field.DeclaringType.Module, true);
var generator = setter.GetILGenerator();
generator.Emit(OpCodes.Ldarg_0);
generator.Emit(OpCodes.Ldarg_1);
generator.Emit(OpCodes.Stfld, field);
generator.Emit(OpCodes.Ret);
return setter.CreateDelegate(delegateType);
}
}
The main difference from the expression tree approach is that readonly fields can also be changed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With