1

This is my simple test code. I want to create field assignment link between 2 objects, and the field is determined at runtime using reflection by caching delegate of its setter/getter method. But, somehow it doesn't work. The assignment is not working; maybe I made a silly mistake. Where am I am wrong?

public static class AssignmentExpression
{
    public static Expression Create(Expression left, Expression right)
    {
        MethodInfo m = typeof(AssignmentExpression)
                  .GetMethod("AssignTo", BindingFlags.NonPublic | BindingFlags.Static)
                  .MakeGenericMethod(left.Type);

        return Expression.Call( null,m,left, right);
    }

    private static void AssignTo<T>(ref T left, T right)  
    {                                                     
        left = right;                                     
    }
}

public class FieldLink
{
    protected Delegate srcGetter;
    protected Delegate dstSetter;

    public FieldLink(FieldInfo srcObject, FieldInfo dstObject)
    {
        this.srcGetter = FieldLink.createGetter(srcObject);
        this.dstSetter = FieldLink.createSetter(dstObject);
    }

    public void update<T>(T dst, T src)
    {
        this.dstSetter.DynamicInvoke(dst, this.srcGetter.DynamicInvoke(src));
    }

    protected static Delegate createGetter(FieldInfo field)
    {
        ParameterExpression objParm = Expression.Parameter(field.DeclaringType, "obj");
        Type delegateType = typeof(Func<,>).MakeGenericType(field.DeclaringType, field.FieldType);
        MemberExpression fieldExpr = Expression.Field(objParm, field.Name);
        LambdaExpression lambda = Expression.Lambda(delegateType, fieldExpr, objParm);
        return lambda.Compile();
    }

    protected static Delegate createSetter(FieldInfo field)
    {
        ParameterExpression objParm = Expression.Parameter(field.DeclaringType, "obj");
        ParameterExpression valueParm = Expression.Parameter(field.FieldType, "value");
        Type delegateType = typeof(Action<,>).MakeGenericType(field.DeclaringType, field.FieldType);
        MemberExpression memberExpr = Expression.Field(objParm, field.Name);
        Expression assignExpr = AssignmentExpression.Create(memberExpr, valueParm);
        LambdaExpression lambda = Expression.Lambda(delegateType, assignExpr, objParm, valueParm);
        return lambda.Compile();
    }
}

public class Test
{
    public int fieldInt = 0;
}

public class TestClass
{
    public Test a = new Test();
    public Test b = new Test();
    public void Start()
    {
        a.fieldInt = 5;

        Debug.Log("before a = " + a.fieldInt + " b = " + b.fieldInt);
        FieldLink testLink = new FieldLink(this.a.GetType().GetField("fieldInt"),
                                           this.b.GetType().GetField("fieldInt"));
        testLink.update(this.b, this.a);
        Debug.Log("after  a = " + a.fieldInt + " b = " + b.fieldInt);
        //here a.fieldInt should be equal to b.fieldInt, but somehow its unchanged!
    }
}
svick
  • 236,525
  • 50
  • 385
  • 514
uray
  • 11,254
  • 13
  • 54
  • 74

1 Answers1

1

Your code appears to work, but maybe not exactly how you're expecting. When you call update you pass in b.fieldInt as the left argument and a.fieldInt as the right argument; the update method then assigns the value of a.fieldInt (5) to the b.fieldInt field, resulting in both objects having a fieldInt value of 5. If you reverse the arguments, both fields end up as zero. Isn't that what you expect?

As an aside - and maybe you have other reasons for using your own AssignmentExpression class - but you can actually replace:

Expression assignExpr = AssignmentExpression.Create(memberExpr, valueParm);

...with:

Expression assignExpr = Expression.Assign(memberExpr, valueParm);

...and you get the same result.

Steve Wilkes
  • 7,085
  • 3
  • 29
  • 32
  • strangely, on my case, after reverted the args, the value is still same. btw my environment is .Net 3.5 so i don't have `Expression.Assign` – uray Feb 04 '13 at 13:00
  • ...it still works for me using your `AssignmentExpression` class running on VS2012, .NET 3.5 - both end up as 5, or both end up as 0. Very odd! – Steve Wilkes Feb 04 '13 at 13:04
  • oh no, I'am using Unity3D environment, which is using Mono 2.6.x, it could be Mono bugs? – uray Feb 04 '13 at 13:06
  • Ahh, right - I don't have that setup available to check, but that sounds like a likely culprit. – Steve Wilkes Feb 04 '13 at 13:08