Чрезмерные события PropertyChanged в WPF

Обычно в установщике свойств объекта мы можем захотеть вызвать событие PropertyChanged, например,

    public event PropertyChangedEventHandler PropertyChanged; 
    protected void Notify(string property)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }

    public string UserNote
    {
        get { return _userNote; }
        set
        {
            _userNote = value;
            Notify("UserNote"); 
        }
    }

В нашей существующей кодовой базе я вижу экземпляры, в которых PropertyChangedEventArgs отправляется null, чтобы указать, что все свойства объекта изменились. Это кажется неэффективным и, по-видимому, приводит к запуску гораздо большего количества событий, чем необходимо. Это также вызывает проблемы, когда объекты обновляют друг друга по кругу.

Это когда-нибудь хорошая практика?

Комментарий в коде пытается это оправдать...

//The purpose of this method is to wire up clients of NotificationBase that are also
//NotificationBases to *their* clients. Consider the following classes:     
         public class ClassA : NotificationBase
         {
             public int Foo
             {
                 get { return 123; }
                 set { Notify("Foo"); }
             }
         }

         public class ClassB : NotificationBase
         {
             ClassA A = new ClassA();
             public ClassB()
             {
                 A.PropertyChanged += AllChanged;
             }
             public void SetFoo()
             {
                 A.Foo = 456;
             }
         }

         public class ClassC
         {
             ClassB B = new ClassB();
             public ClassC()
             {
                 B.PropertyChanged += delegate { dosomething(); };
                 B.SetFoo(); // causes "dosomething" above to be called
             }
         }

        /// ClassB.SetFoo calls ClassA.Foo's setter, which calls ClassA.Notify("Foo").
        /// The event registration in ClassB's ctor causes ClassB.AllChanged to be called, which calls
        /// ClassB.Notify(null) - implying that ALL of ClassB's properties have changed.
        /// The event registration in ClassC's ctor causes the "dosomething" delegate to be called.
        /// So a Notify in ClassA is routed to ClassC via ClassB's PropertyChanged event.

        protected void AllChanged(Object sender, PropertyChangedEventArgs e)
        {
            Notify(null);
        }

Любые мысли очень ценятся.

С уважением, Фзи


person user418689    schedule 12.08.2010    source источник
comment
Вам нужна информация об обычных свойствах или DependencyProperty?   -  person Lukasz Madon    schedule 12.08.2010
comment
Не вдаваясь в подробности, этот код выглядит немного пугающе, а большой комментарий, пытающийся описать назначение метода, кажется мне большим запахом. Что вы на самом деле пытаетесь сделать? Потребитель события Notification должен решить, что ему нужно делать, когда он уведомлен об изменении свойства.   -  person Dave White    schedule 12.08.2010
comment
Например, у нас есть объект Fixture со свойством FixtureStatus. Существует ряд других свойств, которые зависят от FixtureStatus для определения своих значений. Я думаю, что метод Notify следует вызывать с именем каждого из этих зависимых свойств, а не уведомлять все. Однако код начинает немного запутываться из-за вызова Notify для каждого зависимого свойства.   -  person user418689    schedule 12.08.2010


Ответы (3)


На самом деле это проблема с дизайном (или его документацией) PropertyChangedEventArgs. Установка для PropertyName значения null означает, что «все свойства этого объекта изменились». Но если класс не запечатан или вы не используете отражение, вы не можете знать, что все свойства объекта изменились. Самое большее, что вы можете сказать, это то, что все свойства базового класса объекта изменились.

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

С практической точки зрения, то, что вы действительно пытаетесь сделать, это просто вызвать одно событие, которое сообщает слушателям: «целая куча свойств этого объекта изменилась, но я не собираюсь рассказывать вам о них одно за другим. " Когда ты сказал:

Я вижу случаи, когда PropertyChangedEventArgs отправляется как null, чтобы указать, что все свойства объекта изменились. Это кажется неэффективным и, по-видимому, приводит к запуску гораздо большего количества событий, чем необходимо.

... фактическое намерение прямо противоположно. Если метод изменяет свойства Foo, Bar, Baz и Bat объекта, а объект имеет только четыре или пять свойств, вероятно, лучше вызвать одно событие, чем четыре. С другой стороны, если объект имеет шестьдесят свойств, вероятно, будет лучше вызвать четыре события, заставив каждого из слушателей объекта — даже тех, кто не смотрит на эти четыре свойства — делать то, что они делают, когда свойства, о которых они заботятся, изменяются. , потому что эти свойства не были.

Проблема в том, что система уведомлений об изменении свойств в том виде, в каком она была разработана, не является достаточно точным инструментом для каждой отдельной работы. Он разработан, чтобы быть полностью универсальным, и в него не встроено знание конкретной предметной области приложения.

И это, как мне кажется, то, чего не хватает вашему дизайну: знание предметной области.

Во втором примере, если объект Fixture имеет (скажем) десять свойств, которые зависят от значения FixtureStatus, создание десяти событий изменения свойств может показаться немного чрезмерным. Может быть это. Возможно, вместо этого объект должен вызвать событие FixtureStatusChanged. Затем классы со знанием предметной области вашего приложения могут прослушивать одно это событие и игнорировать событие PropertyChanged. (Вы по-прежнему вызываете событие PropertyChanged для других свойств, чтобы объекты, которые не знают, что означает событие FixtureStatusChanged, могли оставаться актуальными, то есть, если вашему классу по-прежнему необходимо реализовать INotifyPropertyChanged один раз. вы реализовали FixtureStatusChanged.)

Дополнительный комментарий: большинство классов во вселенной C#, если они реализуют метод, вызывающий событие Foo, вызывают этот метод OnFoo. Это важное соглашение: оно делает связь между методом и событием явной и позволяет легко распознать тот факт, что код, вызывающий метод, вызывает событие. Notify — это слабое имя для метода в целом — уведомить кого? которого? - и в этом случае это фактически запутывает то, что должно быть сделано явным. Уведомление об изменении свойства достаточно сложно, даже если ваше соглашение об именах не скрывает тот факт, что это происходит.

person Robert Rossney    schedule 12.08.2010

Игнорируя другие вещи, я бы сказал, что только Notify(null) - плохая практика. По своей сути неясно, что это означает, и разработчик, работающий с кодом 5 лет спустя, вероятно, предположил бы, что это означает что-то еще, если бы они не столкнулись с комментариями.

person Rob H    schedule 12.08.2010

Я сталкивался с ситуациями, когда вычисляемые свойства (без сеттеров) должны запускать PropertyChangeNotification, когда какое-то другое свойство я устанавливаю с помощью сеттера.

eg

double Number { get { return num;} set { num=value; При изменении свойства("Число"); OnPropertyChanged("ДваждыЧисло"); } }

двойной TwiceNumber { получить {return _num * 2.0;} }

Как правило, я делаю это только со свойствами get only, и я не понимаю, почему в этом случае свойство, запускающее уведомление об изменении другого, является плохим. Но я думаю, что если я делаю это для любого другого случая, я, скорее всего, не знаю, что делаю!

person NVM    schedule 12.08.2010