views:

1685

answers:

4

Given:

FieldInfo field = <some valid string field on type T>;
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");

How do I compile a lambda expression to set the field on the "target" parameter to "value"?

+14  A: 

You can't. Expression trees don't have a node for field assignment, at least not in .NET 3.5.

In .NET 4, there will indeed be a node for that, as the DLR expression trees are being folded into the System.Linq.Expressions namespace.

To do what you want, you'll have to use System.Reflection.Emit instead. It's not hard though:

class Program
{
    class MyObject
    {
        public int MyField;
    }

    static Action<T,TValue> MakeSetter<T,TValue>(FieldInfo field)
    {
        DynamicMethod m = new DynamicMethod(
            "setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(Program));
        ILGenerator cg = m.GetILGenerator();

        // arg0.<field> = arg1
        cg.Emit(OpCodes.Ldarg_0);
        cg.Emit(OpCodes.Ldarg_1);
        cg.Emit(OpCodes.Stfld, field);
        cg.Emit(OpCodes.Ret);

        return (Action<T,TValue>) m.CreateDelegate(typeof(Action<T,TValue>));
    }

    static void Main()
    {
        FieldInfo f = typeof(MyObject).GetField("MyField");

        Action<MyObject,int> setter = MakeSetter<MyObject,int>(f);

        var obj = new MyObject();
        obj.MyField = 10;

        setter(obj, 42);

        Console.WriteLine(obj.MyField);
        Console.ReadLine();
    }
}
Barry Kelly
Great response barry, you answered my initial question. I'm going to post another question where I need op codes for calling a conversion first.... THANKS!
TheSoftwareJedi
Just curious, what is the difference of this approach versus just using System.Reflection and MemberInfos to set the property?
chakrit
chakrit - it's faster.
Barry Kelly
I'm terribly surprised with your answer +1. nice job!, congrats : )
SDReyes
Very useful stuff, but beware of edge cases! Value type cases are not handled in this MakeSetter method.
Oleg Mihailik
+1  A: 

I think you can actually, please note the code below is just a snippet:

Create a MethodCallExpression and call the FieldInfo.SetValue() method:

MethodInfo setValueInfo = typeof(FieldInfo).GetMethod("SetValue");
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
Expression setExp = Expression.Call(targetExp, setValueInfo, targetExp, valueExp);

Still uses reflection though.

Hope that helps!

Zachary Yates
Still using reflection. No good, I'm going for speed.
TheSoftwareJedi
@TheSoftwareJedi - once you have called Compile, it will be very fast. The bigger problem is that this calls a method, not set a field.
Marc Gravell
Hmm, I see. I thought that you were compiling reflection into the lambda. I see that you aren't. Interesting! I'll give it a shot!
TheSoftwareJedi
Ahh... I might have misread it! It is indeed burning reflection into the lambda, so slowness will ensue... you might just as well call fieldInfo.SetValue as this lambda. My bad...
Marc Gravell
yeah - it is compiling reflection into the lambda. no dice
TheSoftwareJedi
As it's not achieving the desired effect, it better be downvoted perhaps.
Oleg Mihailik
+6  A: 

Setting a field is, as already discussed, problematic. You can can (in 3.5) a single method, such as a property-setter - but only indirectly. This gets much easier in 4.0, as discussed here. However, if you actually have properties (not fields), you can do a lot simply with Delegate.CreateDelegate:

using System;
using System.Reflection;
public class Foo
{
    public int Bar { get; set; }
}
static class Program
{
    static void Main()
    {
        MethodInfo method = typeof(Foo).GetProperty("Bar").GetSetMethod();
        Action<Foo, int> setter = (Action<Foo, int>)
            Delegate.CreateDelegate(typeof(Action<Foo, int>), method);

        Foo foo = new Foo();
        setter(foo, 12);
        Console.WriteLine(foo.Bar);
    }
}
Marc Gravell
I would love to hear why that got down-voted... seems a pretty decent side-point to me; only applies to properties, but avoids the need for either Reflection.Emit or Expression...
Marc Gravell
Marc, unless I'm mistaken, I had my answer unselected last night too - I went from 3056 down to 3041 this morning. This also happened on my previous answer to TheSoftwareJedi last time. Seems oddly passive-aggressive. In any case, +1 from me.
Barry Kelly
@Barry - indeed! Really curious...
Marc Gravell
Why you you have no rep for this answer is a mistery to me. +1 as this was exactly what I needed...
flq
Glad it helped ;-p
Marc Gravell
+1  A: 
private static Action<object, object> CreateSetAccessor(FieldInfo field)
 {
  DynamicMethod setMethod = new DynamicMethod(field.Name, typeof(void), new[] { typeof(object), typeof(object) });
  ILGenerator generator = setMethod.GetILGenerator();
  LocalBuilder local = generator.DeclareLocal(field.DeclaringType);
  generator.Emit(OpCodes.Ldarg_0);
  if (field.DeclaringType.IsValueType)
  {
   generator.Emit(OpCodes.Unbox_Any, field.DeclaringType);
   generator.Emit(OpCodes.Stloc_0, local);
   generator.Emit(OpCodes.Ldloca_S, local);
  }
  else
  {
   generator.Emit(OpCodes.Castclass, field.DeclaringType);
   generator.Emit(OpCodes.Stloc_0, local);
   generator.Emit(OpCodes.Ldloc_0, local);
  }
  generator.Emit(OpCodes.Ldarg_1);
  if (field.FieldType.IsValueType)
  {
   generator.Emit(OpCodes.Unbox_Any, field.FieldType);
  }
  else
  {
   generator.Emit(OpCodes.Castclass, field.FieldType);
  }
  generator.Emit(OpCodes.Stfld, field);
  generator.Emit(OpCodes.Ret);
  return (Action<object, object>)setMethod.CreateDelegate(typeof(Action<object, object>));
 }
DoubleDown