2014-10-30 5 views
2

Я использую следующий код для кэширования свойство геттер/сеттер делегатов для быстрого доступа к этой функциональности:Использование выражений для доступа к свойствам структуры в 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; 
    } 
} 

Это работает очень хорошо, когда доступ к свойствам объектов класса , но он терпит неудачу, когда я использую его для объекта 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 до 10. Однако, поскольку LocationStruct является структурой, то testLocationStruct передается по значению, то значение (внутренний для метод, вызываемый делегатом) get имеет свой X, установленный в 10, но объект testLocationStruct в вышеуказанном блоке кода остается неизменным.

Итак, мне нужна методология доступа к свойствам структурных объектов, аналогичных приведенным выше (что работает только для свойств объектов класса). Я попытался выполнить это, используя шаблоны «пройти по ссылке», но я просто не могу заставить его работать.

Может ли кто-нибудь предоставить аналогичные методы BuildGetter и BuildSetter, которые могут использоваться для кэширования делегатов getter/setter для значений свойств struct?

+0

Краткое примечание: Это не называется лямбда-выражения, только выражения или деревья выражений. Lambdas больше ссылается на замыкания, то есть анонимные функции на C#. – metacubed

+0

Rgr ... thx для заметки. Я изменю свой заголовок и тег. – FTLPhysicsGuy

+1

Просьба уточнить, что об этом не работает для типов значений (structs). Вы сталкиваетесь с проблемой обработки типов значений в штучной упаковке? Если да, можно ли это решить, изменив свой код, чтобы он был общим, а не предполагал System.Object?Вы должны опубликовать код, который демонстрирует использование вашей реализации для типов значений, четко показывая, как это не работает для вас. –

ответ

1

Вы должны заботиться из двух вещей для того, чтобы это работало:

  1. При создании сеттера дерева выражений, вам нужно использовать Expression.Unbox для типов значений и Expression.Convert для ссылочных типов.
  2. При вызове сеттера с типом значения вам нужно убедиться, что он установлен в бокс, чтобы установить значение, используя указатель на структуру (а не работу с копией структуры).

Новая реализация выглядит следующим образом (показывая только новые методы инкубационных и тестирование, так как все остальное то же самое):

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

0

Структура в качестве параметра передаются по значению, и исх/из, кажется, не хорошо работать с выражениями, вы можете рассмотреть использовать новую сигнатуру функции, которая возвращает экземпляр структуры вместо:

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), он попал в исключение, пытаясь определить выражение. Исключение: выражение типа System.Double не может использоваться для параметра типа System.Object метода System.Object <.cctor> b__0 (System.Reflection.MethodInfo, System.Object, System.Object). Я думаю, что могу исправить эту проблему, но, возможно, более подробно - методология не будет работать для меня, если мне придется заменить исходную структуру на результаты вызова. – FTLPhysicsGuy