2014-10-30 50 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