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?
You need to take care of two things in order for this to work:
Expression.Unbox
for value types and Expression.Convert
for reference types.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