Если возникает необходимость одинаковой обработки присвоения значения свойству объекта, то можно воспользоваться лямбда выражениями. Такая задача может возникнуть при проверке или обработке значений, логгировании присвоения и т.д.

Простейший метод, который не учитывает присвоение свойствам вложенных объектов, будет такой:

    public static class ObjectHelper
    {
        public static void Set<T, TProp>(this T obj, Expression<Func<T, TProp>> property, TProp value)
        {
            if (obj == null) throw new ArgumentNullException("obj");
            if (property == null) throw new ArgumentNullException("property");

            var memberExpression = (MemberExpression) property.Body;
            var targetPropertyInfo = (PropertyInfo) memberExpression.Member;

            // здесь можно дополнительно обработать obj или value согласно бизнес логике

            targetPropertyInfo.SetValue(obj, value);
        }
    }

Это сработает только для присвоения свойству самого объекта, но не свойству вложенного объекта. То есть:

            myObject.Set(x => x.MyProperty, "bla-bla-bla"); // РАБОТАЕТ
            myObject.Set(x => x.MyProperty.InnerProperty, "bla-bla-bla"); // НЕ РАБОТАЕТ

Не работает, потому что присвоение в данном случае идет не объекту в myObject.MyProperty, а объекту myObject.
(Причем если типы у MyProperty и myObject одинаковые, то не будет выброшено исключение, и в программе будет скрытая ошибка!)

Для того чтобы сработало и для свойств вложенных объектов, нужно пройтись по дереву выражений и получить нужный объект, свойству которого и будет присваиваться значение:

    public static class ObjectHelper
    {
        public static void Set<T, TProp>(this T obj, Expression<Func<T, TProp>> property, TProp value)
        {
            if (obj == null) throw new ArgumentNullException("obj");
            if (property == null) throw new ArgumentNullException("property");

            object target = obj;
            var memberExpression = (MemberExpression) property.Body;
            var targetPropertyInfo = (PropertyInfo) memberExpression.Member;

            if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
            {
                var expressions = new Stack<MemberExpression>();

                while (memberExpression.Expression.NodeType != ExpressionType.Parameter)
                {
                    memberExpression = (MemberExpression)memberExpression.Expression;
                    expressions.Push(memberExpression);
                }

                while (expressions.Count > 0)
                {
                    var expression = expressions.Pop();
                    var propertyInfo = (PropertyInfo)expression.Member;
                    target = propertyInfo.GetValue(target);
                    if (target == null) throw new NullReferenceException(expression.ToString());
                }
            }

            // здесь можно дополнительно обработать obj, target или value согласно бизнес логике

            targetPropertyInfo.SetValue(target, value);
        }
    }

            myObject.Set(x => x.MyProperty, "bla-bla-bla"); // РАБОТАЕТ
            myObject.Set(x => x.MyProperty.InnerProperty, "bla-bla-bla"); // РАБОТАЕТ

Комментарии (7)


  1. capslocky
    04.06.2015 20:05

    Мсье знает толк в извращениях


    1. rinader Автор
      04.06.2015 20:41

      Ну покажите не «извращенное», по вашему, решение.
      Здесь можно в лог записать левый операнд присваивания, например:
      Trace.WriteLine(string.Format("{0} < — {1}", property.Body, value));
      А вы бы как реализовали?


      1. capslocky
        05.06.2015 06:08
        +1

        Попробуйте применить аспектно-ориентированный подход. Вот небольшая статья об этом.


        1. rinader Автор
          05.06.2015 07:50
          -2

          Про PostSharp я знаю.

          Он вносит изменения в MSIL, а мне бы этого не хотелось.

          Кроме этого если доступа к классу нет, то как
          «Проатрибутить» свойства?
          Кроме этого при логировании, например, мы не узнаем откуда было присвоение.


          1. VladVR
            05.06.2015 08:48
            +3

            Страшно представить. А можно все-таки сценарий, для чего может понадобиться логгирование присваивания свойств, да еще и у объекта неподконтрольного класса?

            Если аспекты не применить, то я бы стал решать это через t4 template. Можно сгенерировать любую оболочку(прокси, декоратор, что угодно). Раз уж все равно пришлось переписать весь код с «a.Prop = value» на «a.Set(a=>a.Prop, value)», то почему бы его не переписать на «new proxyA(a).Prop = value». И если кода с возможными присвоениями много, а точка входа одна, то new можно вызвать в одном месте, а дальше весь код останется вообще не тронут.


            1. rinader Автор
              05.06.2015 09:04

              Страшно представить.
              Да вот представьте есть такое в проекте, что идет логирование и проверка значения присваивания, причем один и тот же код повторяется много раз. В класс влезть нельзя.
              Я ничего более подходящего не придумал нежели переписать так.
              Насчет прокси я тоже думал, но тогда нужно каждое свойство поддержать, а кроме этого в сеттере вам не известно само левое выражение присваивания, а только правое выражение.
              Нужно было залогировать все левое выражение (весь путь через точку).
              А также неизвестен реальный callermembername.
              (Я в примерн просто не стал приводить все параметры set).