2014-10-30 48 views
2

我一直在使用下面的代码缓存,以便快速访问该功能属性的getter/setter代表:使用表达式在C#访问结构特性

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

这工作得很好访问类对象的属性时,但是当我将它用于结构对象时它失败了。例如,考虑下面的代码:

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对象保持不变。因此,我需要一种方法来访问与上面类似的结构对象的属性(它只适用于类对象的属性)。我试图使用“通过引用”模式来完成此操作,但我无法使其工作。

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

+0

快速注:这些都不是所谓的lambda表达式,只是表达式或表达式树。 Lambdas更多地指的是闭包,即C#中的匿名函数。 – metacubed 2014-10-30 01:21:33

+0

Rgr ... thx注释。我会改变我的标题和标签。 – FTLPhysicsGuy 2014-10-30 01:29:01

+1

请详细说明这不适用于值类型(结构)。你是否遇到了处理盒装值类型的问题?如果是这样,它可以通过更改您的代码,以便它通用而不是假定System.Object?您应该发布代码,演示如何使用您的实现值类型,清楚地显示如何不适合你。 – 2014-10-30 01:43:31

回答

1

你需要照顾在为了两件事情这个工作:

  1. 在创建二传手表达式树,你需要使用Expression.Unbox值类型和Expression.Convert引用类型。
  2. 当使用值类型调用setter时,需要确保使用指向结构的指针设置值(而不是在结构的副本上工作)。

新的实现看起来像这样(只显示新的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; 
} 

和代码来调用编译二传手:

... 
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

+0

这真是一个很棒的答案。谢谢! – FTLPhysicsGuy 2016-10-04 04:40:29

0

结构的参数是按值传递,和REF /出似乎不表现很好地工作,你可以考虑使用新功能的签名,返回一个结构实例,而不是:

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

感谢您的回复。我将你的代码复制粘贴到测试类中,但是当我从上面的代码中使用xProperty并调用了你的BuildSetter5(xProperty)时,它在尝试定义expr时遇到了异常。异常说:System.Double类型的表达式不能用于方法System.Object的System.Object类型的参数<.cctor> b__0(System.Reflection.MethodInfo,System.Object,System.Object)。我认为我可以解决这个问题,但是 - 也许更重要的一点 - 如果我必须用调用结果替换原始结构,方法将不适用于我。 – FTLPhysicsGuy 2014-10-30 23:35:09