使用表达式访问C#中的struct属性

时间:2014-10-30 01:13:56

标签: c# struct linq-expressions propertyinfo

我一直在使用以下代码来缓存属性getter / setter委托,以便快速访问该功能:

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;
    }
}

这在访问类对象的属性时非常有效,但是当我将它用于struct对象时它会失败。例如,请考虑以下代码:

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!");
        }
    }
}

第一部分工作,将testLocationClass的X值设置为10.但是,因为LocationStruct是一个结构,testLocationStruct是通过值传递的,该值(委托调用的方法的内部)得到了它的X设置为10,但上面代码块中的testLocationStruct对象保持不变。

因此,我需要一种方法来访问与上面类似的struct对象的属性(仅适用于类对象的属性)。我试图用#34;传递参考&#34;模式,但我无法让它发挥作用。

任何人都可以提供类似的BuildGetter和BuildSetter方法,可用于缓存结构属性值的getter / setter委托吗?

2 个答案:

答案 0 :(得分:1)

为了实现这一点,你需要处理两件事:

  1. 创建setter表达式树时,需要对值类型使用Expression.Unbox,对引用类型使用Expression.Convert
  2. 当使用值类型调用setter时,您需要确保使用指向struct的指针来设置值(而不是处理结构的副本)。
  3. 新实现看起来像这样(只显示新的setter和测试方法,因为其余部分是相同的):

    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;
    }
    

    调用编译的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;
    ...
    

    打印:

    Worked for the class!
    Worked for the struct!
    

    我还准备了一个.Net小提琴,显示了这里的工作实现:https://dotnetfiddle.net/E6WZmK

    有关Expression.Unbox步骤的解释,请参阅此答案:https://stackoverflow.com/a/32158735/521773

答案 1 :(得分:0)

作为参数的结构是按值传递的,并且ref / out似乎不适用于表达式,您可以考虑使用返回结构实例的新函数签名:

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;
}