3

I've been using the following code to cache property getter/setter delegates for quick access to that functionality:

class PropertyHelper
{
    public static Func<object, object> BuildGetter(PropertyInfo propertyInfo)
    {
        var method = propertyInfo.GetGetMethod(true);

        var obj = Expression.Parameter(typeof(object), "o");
        Expression<Func<object, object>> expr =
                Expression.Lambda<Func<object, object>>(
                        Expression.Convert(
                                Expression.Call(
                                        Expression.Convert(obj, method.DeclaringType),
                                        method),
                                typeof(object)),
                        obj);
        return expr.Compile();
    }

    public static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
    {
        var method = propertyInfo.GetSetMethod(true);

        var obj = Expression.Parameter(typeof(object), "o");
        var value = Expression.Parameter(typeof(object));

        Expression<Action<object, object>> expr =
            Expression.Lambda<Action<object, object>>(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method,
                    Expression.Convert(value, method.GetParameters()[0].ParameterType)),
                obj,
                value);

        Action<object, object> action = expr.Compile();
        return action;
    }
}

This works quite well when accessing properties of class objects, but it fails when I use it for a struct object. For example, consider the following code:

public struct LocationStruct
{
    public double X { get; set; }
    public double Y { get; set; }
}

public class LocationClass
{
    public double X { get; set; }
    public double Y { get; set; }
}

public class Tester
{
    public static void TestSetX()
    {
        Type locationClassType = typeof(LocationClass);
        PropertyInfo xProperty = locationClassType.GetProperty("X");
        Action<object, object> setter = PropertyHelper.BuildSetter(xProperty);

        LocationStruct testLocationClass = new LocationClass();
        setter(testLocationClass, 10.0);
        if (testLocationClass.X == 10.0)
        {
            MessageBox.Show("Worked for the class!");
        }


        Type locationStructType = typeof(LocationStruct);
        xProperty = locationStructType.GetProperty("X");
        setter = PropertyHelper.BuildSetter(xProperty);

        LocationStruct testLocationStruct = new LocationStruct();
        setter(testLocationStruct, 10.0);
        if (testLocationStruct.X != 10.0)
        {
            MessageBox.Show("Didn't work for the struct!");
        }
    }
}

The first part works, setting the X value of testLocationClass to 10. However, because LocationStruct is a struct, the testLocationStruct is passed in by value, that value (internal to the method called by the delegate) get's its X set to 10, but the testLocationStruct object in the above code block remains unchanged.

So, I need a methodology for accessing properties of struct objects similar to the one above (which only works for properties of class objects). I've tried to accomplish this using "pass by reference" patterns, but I just can't get it to work.

Can anyone provide similar BuildGetter and BuildSetter methods that could be used to cache getter/setter delegates for struct property values?

FTLPhysicsGuy
  • 1,035
  • 1
  • 11
  • 23
  • Quick note: These are not called lambda expressions, just Expressions or Expression Trees. Lambdas refer more to closures i.e. anonymous functions in C#. – metacubed Oct 30 '14 at 01:21
  • Rgr... thx for the note. I'll change my title and tag. – FTLPhysicsGuy Oct 30 '14 at 01:29
  • 1
    Please elaborate on what about this doesn't work for value types (structs). Are you running into the problem of handling boxed value types? If so, can it be addressed by changing your code so that it's generic instead of assuming System.Object? You should post code which demonstrates using your implementation for value types, showing clearly how that's not working for you. – Peter Duniho Oct 30 '14 at 01:43
  • Peter, thanks for the comment. I added more detail and an example. – FTLPhysicsGuy Oct 30 '14 at 03:37
  • possible duplicate of [Generate dynamic method to set a field of a struct instead of using reflection](http://stackoverflow.com/questions/1272454/generate-dynamic-method-to-set-a-field-of-a-struct-instead-of-using-reflection) – Andrew Savinykh Aug 03 '15 at 09:39

2 Answers2

3

You need to take care of two things in order for this to work:

  1. When creating the setter expression tree, you need to use Expression.Unbox for value types and Expression.Convert for reference types.
  2. When calling the setter with the value type, you need to make sure it is boxed to set the value using a pointer to the struct (rather than working on a copy of the struct).

The new implementation looks like this (only showing the new setter and test methods, since the rest is the same):

public static Action<object, object> BuildSetter(PropertyInfo propertyInfo)
{
    // Note that we are testing whether this is a value type
    bool isValueType = propertyInfo.DeclaringType.IsValueType;
    var method = propertyInfo.GetSetMethod(true);
    var obj = Expression.Parameter(typeof (object), "o");
    var value = Expression.Parameter(typeof (object));

    // Note that we are using Expression.Unbox for value types
    // and Expression.Convert for reference types
    Expression<Action<object, object>> expr = 
        Expression.Lambda<Action<object, object>>(
            Expression.Call(
                isValueType ? 
                    Expression.Unbox(obj, method.DeclaringType) :
                    Expression.Convert(obj, method.DeclaringType), 
                method, 
                Expression.Convert(value, method.GetParameters()[0].ParameterType)), 
                obj, value);
    Action<object, object> action = expr.Compile();
    return action;
}

And the code to call the compiled setter:

...
Type locationStructType = typeof (LocationStruct);
xProperty = locationStructType.GetProperty("X");
setter = PropertyHelper.BuildSetter(xProperty);
LocationStruct testLocationStruct = new LocationStruct();

// Note the boxing of the struct before calling the setter
object boxedStruct = testLocationStruct;
setter(boxedStruct, 10.0);
testLocationStruct = (LocationStruct)boxedStruct;
...

This prints:

Worked for the class!
Worked for the struct!

I have also prepared a .Net fiddle that shows the working implementation here: https://dotnetfiddle.net/E6WZmK

See this answer for an explanation of the Expression.Unbox step: https://stackoverflow.com/a/32158735/521773

Community
  • 1
  • 1
Gedde
  • 1,150
  • 10
  • 19
0

Structure as parameter are passed by value, and ref/out seems do not work well with expressions, you can consider use new function signature that returns a struct instance instead:

static Func<MethodInfo, object, object, object> s1 = (MethodInfo set, object instance, object val) =>
{
    set.Invoke(instance, new object[] { val });
    return instance;
};

// Non-Generic approach
static Func<object, object, object> BuildSetter5(PropertyInfo propertyInfo)
{
    var method = propertyInfo.GetSetMethod(true);

    var obj = Expression.Parameter(typeof(object), "o");
    var value = Expression.Parameter(typeof(object));

    Expression<Func<object, object, object>> expr =
        Expression.Lambda<Func<object, object, object>>(
            Expression.Call(
                s1.Method,
                Expression.Constant(method),
                obj,
                Expression.Convert(value, method.GetParameters()[0].ParameterType)),
            obj,
            value);

    Func<object, object, object> action = expr.Compile();
    return action;
}

// Generic approach
static Func<T, object, T> BuildSetter6<T>(PropertyInfo propertyInfo) where T : struct
{
    var method = propertyInfo.GetSetMethod(true);

    var obj = Expression.Parameter(typeof(T), "o");
    var value = Expression.Parameter(typeof(object));

    Expression<Func<T, object, T>> expr =
        Expression.Lambda<Func<T, object, T>>(
            Expression.Convert(
                Expression.Call(
                    s1.Method,
                    Expression.Constant(method),
                    Expression.Convert(obj, typeof(object)),
                    Expression.Convert(value, method.GetParameters()[0].ParameterType)),
                typeof(T)),
            obj,
            value);

    Func<T, object, T> action = expr.Compile();
    return action;
}
Karata
  • 1,079
  • 8
  • 16
  • Thanks for the reply. I copy-pasted your code into my tester class, but when I used xProperty from my code above and called your BuildSetter5(xProperty), it hit an exception when trying to define expr. The exception said: Expression of type System.Double cannot be used for parameter of type System.Object of method System.Object<.cctor>b__0(System.Reflection.MethodInfo, System.Object, System.Object). I think I can fix the issue, but -- perhaps more to the point -- the methodology won't work for me if I have to replace the original struct with the results of the call. – FTLPhysicsGuy Oct 30 '14 at 23:35