Если возникает необходимость одинаковой обработки присвоения значения свойству объекта, то можно воспользоваться лямбда выражениями. Такая задача может возникнуть при проверке или обработке значений, логгировании присвоения и т.д.
Простейший метод, который не учитывает присвоение свойствам вложенных объектов, будет такой:
Это сработает только для присвоения свойству самого объекта, но не свойству вложенного объекта. То есть:
Не работает, потому что присвоение в данном случае идет не объекту в 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");
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"); // РАБОТАЕТ
capslocky
Мсье знает толк в извращениях
rinader Автор
Ну покажите не «извращенное», по вашему, решение.
Здесь можно в лог записать левый операнд присваивания, например:
Trace.WriteLine(string.Format("{0} < — {1}", property.Body, value));
А вы бы как реализовали?
capslocky
Попробуйте применить аспектно-ориентированный подход. Вот небольшая статья об этом.
rinader Автор
Про PostSharp я знаю.
Он вносит изменения в MSIL, а мне бы этого не хотелось.
Кроме этого если доступа к классу нет, то как
«Проатрибутить» свойства?
Кроме этого при логировании, например, мы не узнаем откуда было присвоение.
VladVR
Страшно представить. А можно все-таки сценарий, для чего может понадобиться логгирование присваивания свойств, да еще и у объекта неподконтрольного класса?
Если аспекты не применить, то я бы стал решать это через t4 template. Можно сгенерировать любую оболочку(прокси, декоратор, что угодно). Раз уж все равно пришлось переписать весь код с «a.Prop = value» на «a.Set(a=>a.Prop, value)», то почему бы его не переписать на «new proxyA(a).Prop = value». И если кода с возможными присвоениями много, а точка входа одна, то new можно вызвать в одном месте, а дальше весь код останется вообще не тронут.
rinader Автор
Я ничего более подходящего не придумал нежели переписать так.
Насчет прокси я тоже думал, но тогда нужно каждое свойство поддержать, а кроме этого в сеттере вам не известно само левое выражение присваивания, а только правое выражение.
Нужно было залогировать все левое выражение (весь путь через точку).
А также неизвестен реальный callermembername.
(Я в примерн просто не стал приводить все параметры set).