Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generate dynamic method to set a field of a struct instead of using reflection

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?

  1. Generate CIL that invoke the setter using reflection (as the 1st code segment in this post). This makes no sense, given the requirement is to get rid of reflection, but it's a possible implementation so I just mention.
  2. Instead of using Action<object, object>, use a custom delegate whose signature is public delegate void Setter(ref object target, object value).
  3. Instead of using 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.

like image 300
Buu Nguyen Avatar asked Aug 13 '09 14:08

Buu Nguyen


2 Answers

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));
}
like image 83
ghord Avatar answered Oct 18 '22 09:10

ghord


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.

like image 43
artelk Avatar answered Oct 18 '22 07:10

artelk