Получить экземпляр объекта, который содержит указанное свойство - PullRequest
1 голос
/ 31 июля 2010

Этот вопрос, вероятно, потребуется время, чтобы объяснить, и мне нужно будет предоставить справочную информацию ...

Это просто то, с чем я играю и не для производства, но наВ тот момент, когда у меня есть некоторый код, который выглядит следующим образом:

var myDataModel = new DataModel();

myDataModel.PropertyChanged += myDataModel_PropertyChanged;

myDataModel.ChangeProperty(t => t.TestValue, 2);

Итак, вместо непосредственного использования myDataModel.TestValue = 2, я использую метод расширения ChangeProperty, чтобы я мог обрабатывать все события изменений иделать все, что я хочу в одном месте.Мой метод расширения выглядит следующим образом (и да, я знаю, что он хакерский):

public static class NotifyPropertyChangedExtensions
{
    public static void ChangeProperty<T, U>(
                                      this T instance, 
                                      Expression<Func<T, U>> propertyToChange, 
                                      U newValue)
    {
        var member = propertyToChange.Body as MemberExpression;

        if (member != null)
        {
            if (!propertyToChange.Compile().Invoke(instance).Equals(newValue))
            {
                var setProperty = instance.GetType().GetProperty(
                                      member.Member.Name, 
                                        BindingFlags.SetProperty | 
                                        BindingFlags.Public | 
                                        BindingFlags.Instance);

                if (setProperty != null)
                {
                    // actually set the property
                    setProperty.SetValue(instance, newValue, null);

                    // raise the property changed event
                    if (typeof(INotifyPropertyChanged).IsAssignableFrom(
                                                                 typeof(T)))
                    {
                        var delegatesToCall = 
                             instance.GetType().GetField("PropertyChanged", 
                                       BindingFlags.Instance | 
                                       BindingFlags.NonPublic)
                                  .GetValue(instance) as MulticastDelegate;


                        if (delegatesToCall != null)
                        {
                            var eventArgs = new PropertyChangedEventArgs(
                                                           setProperty.Name);
                            foreach (var @delegate in 
                                         delegatesToCall.GetInvocationList())
                            {
                                @delegate.Method.Invoke(
                                       @delegate.Target, 
                                       new object[] { instance, eventArgs });
                            }
                        }

                    }
                }
            }
        }
        else
        {
            throw new ArgumentException(
                    string.Format(
                           "Cannot determine the property to change {0}", 
                           propertyToChange));
        }
    }
}

С этой архитектурой моя модель данных довольно чиста:

public class DataModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public int TestValue { get; set; }
} 

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

Теперь, что я на самом деле хочу сделать, это нечто ближе к этому:

var dataModel = new DataModel();

myDataModel.PropertyChanged += myDataModel_PropertyChanged;

myDataModel.TestValue.Set(2); // this is what I want...

Итак, я 'Я думаю, что мне в основном понадобится метод расширения - но я могу видеть только то, как отправить само свойство (в данном случае TestValue) и новое значение.Тогда я подумал, можно ли, учитывая свойство, узнать экземпляр класса, к которому оно принадлежит?

1 Ответ

1 голос
/ 31 июля 2010
  1. Не делай этого. Это нарушает инкапсуляцию.

  2. Нет. В myDataModel.TestValue.Set(2); метод расширения всегда вызывается для значения, возвращаемого свойством. Нет способа получить класс, экземпляр или свойство, которое вернуло значение.

  3. Вы можете сделать что-то вроде этого:

    var t = new DataModel();
    ((Expression<Func<int>>)(() => t.Foo)).Set(100);
    

    с

    static class Extensions
    {
        public static void Set<T>(this Expression<Func<T>> expression, T value)
        { ... }
    }
    

    но это уродливо, почти нечитабельно, неясно, неэффективно и подвержено ошибкам.

  4. Вы ищете Аспектно-ориентированное программирование (AOP).

    Посмотрите на PostSharp или LinFu.

  5. Пока нет действительно чистого решения для реализации INotifyPropertyChanged. Если ввод всех установщиков свойств слишком трудоемкий или чреват ошибками, я сгенерирую их с помощью шаблона T4.

...