INotifyPropertyChanged против DependencyProperty во ViewModel

353

При реализации ViewModel в WPF-приложении с архитектурой Model-View-ViewModel кажется, что существует два основных способа сделать его доступным для данных. Я видел реализации, которые используют DependencyPropertyдля свойств, с которыми будет связываться View, и INotifyPropertyChangedвместо этого я видел реализацию ViewModel .

Мой вопрос: когда я должен предпочесть одно другому? Есть ли различия в производительности? Это действительно хорошая идея, чтобы дать зависимости ViewModel для WPF? Что еще нужно учитывать при принятии проектного решения?

bitbonk
источник
11
см. stackoverflow.com/questions/1329138/… для проверенного компилятором способа реализации INotifyPropertyChanged. Избегайте использования имен свойств в качестве магической строки.
Ян Рингроз
10
Как правило, существует большое различие между свойством зависимости и обычным свойством в классе, который реализует INotifyPropertyChanged. Свойства зависимостей могут быть исходными или целевыми в привязке данных, но обычные свойства с поддержкой INotifyPropertyChanged могут использоваться только в качестве источника. Таким образом, эти решения не являются полностью взаимозаменяемыми. Инфраструктура привязки данных требует DP в качестве цели для работы, но источником может быть либо обычное свойство с поддержкой INotifyPropertyChanged, либо общий DP.
Мостафа Резаи
4
См. Stackoverflow.com/a/10595688/200442 для способа реализации .net 4.5 INotifyPropertyChanged.
Дэниел Литтл
лучше всего объяснить здесь stackoverflow.com/a/3552550/366064
Бижан

Ответы:

214

Кент написал интересный блог на эту тему: View Models: POCOs против DependencyObjects .

Краткое содержание:

  1. DependencyObjects не помечены как сериализуемые
  2. Класс DependencyObject переопределяет и закрывает методы Equals () и GetHashCode ()
  3. DependencyObject имеет привязку к потоку - доступ к нему возможен только в том потоке, в котором он был создан.

Я предпочитаю подход POCO. Базовый класс для PresentationModel (он же ViewModel), который реализует интерфейс INotifyPropertyChanged, можно найти здесь: http://compositeextensions.codeplex.com

JBE
источник
24
DependencyObject также принимает зависимость от библиотек WPF, в то время как POCO - нет, что позволяет моделям представлений управлять другим стеком пользовательского интерфейса, где WPF недоступен (Compact Framework, Mono).
Codekaizen
26
Понятно, что свойства зависимости строятся исключительно для пользовательского интерфейса, а не для бизнес-уровня.
Андрей Ринея
11
Свойства зависимости также требуют родителя DependencyObject. Ваша ViewModel действительно не должна наследоваться от DependencyObject.
Гусдор
38

Согласно руководству по производительности WPF, DependencyObjects определенно работают лучше, чем POCO, которые реализуют INotifyPropertyChanged:

http://msdn.microsoft.com/en-us/library/bb613546.aspx

Джеймс Эшли
источник
1
Я должен согласиться с этим ;-): blog.lexique-du-net.com/index.php?post/2010/02/24/…
Джоната АНТУНА
Если вы выберете .NET Framework версии 4, ссылка все равно будет работать. Это просто не доступно для "текущей версии".
doubleYou
Спасибо за указание на это, есть много скандальной дезинформации разработчиков, делающих грубые заявления о том, что INotifyPropertyChanged быстрее или требует меньше накладных расходов, чем DP, и это просто необоснованно. DP - это быстрые, элегантные и мощные способы структурного определения виртуального дерева (данных).
tpartee
В объектах DependencyObjects скрыто зло. Они должны быть созданы в том же потоке, что и связанные с ними элементы управления. Это означает поток GUI. Это означает, что вам нужно отправить творение в этот поток. Например, вы не можете загружать и создавать эти вещи в некотором фоновом потоке из БД. Если вы не отправите творение. Безумный.
ed22
28

Выбор полностью зависит от вашей бизнес-логики и уровня абстракции пользовательского интерфейса. Если вы не хотите хорошего разделения, DP будет работать для вас.

DependencyProperties будет применяться в основном на уровне VisualElements, поэтому не будет хорошей идеей, если мы создадим много DP для каждого из наших бизнес-требований. Также для DP существует более высокая стоимость, чем для INotifyPropertyChanged. Когда вы разрабатываете WPF / Silverlight, постарайтесь спроектировать UI и ViewModel полностью раздельно, чтобы в любой момент мы могли изменить элементы управления Layout и UI (на основе темы и стилей).

См. Также этот пост - /programming/275098/what-applications-could-i-study-to-understand-datamodel-view-viewmodel . Ссылка имеет много ссылок на шаблон Model-View-ViewModel, который очень важен для этого обсуждения.

Джоби Джой
источник
9
Пост от jbe более точно отвечает на различия. Тот факт, что виртуальная машина (или Presenter) наследуется от DependencyObject, не означает, что ее нельзя стилизовать или логически не отделить от View, это просто означает, что хранилище для значений свойств отличается от явно объявленных полей в Стиль поко. При этом сериализация, логическое равенство и сходство потоков являются реальными проблемами, с которыми сталкиваются виртуальные машины на основе DepedencyObject.
Майахтан
«Кроме того, для DP более высокая стоимость, чем для INotifyPropertyChanged», - где ваш источник доказательств по этому поводу? Многие разработчики делают это заявление без каких-либо доказательств, подтверждающих это. Согласно MSDN это не так. «попытайтесь спроектировать пользовательский интерфейс и ViewModel полностью раздельно, чтобы в любой момент мы могли изменить макет и элементы управления пользовательским интерфейсом» - опять же, это не имеет абсолютно никакого отношения к POCO + PropChange по сравнению с DO / DP. Во всяком случае, реестр Reflection and Path в DO / DP улучшает вашу способность работать на визуальной стороне.
tpartee
20

С точки зрения выразительности, я полностью наслаждаюсь использованием свойств зависимости и съеживаюсь при мысли INotifyPropertyChanged. Помимо stringимен свойств и возможных утечек памяти из-за подписки на события, INotifyPropertyChangedсуществует гораздо более явный механизм.

Свойства зависимостей подразумевают «когда это, делай это», используя понятные статические метаданные. Это декларативный подход, который получает мой голос за элегантность.

Брайан Уоттс
источник
1
Строковая часть теперь имеет решение с оператором nameof.
Newtopian
@Newtopian: правда. Есть также несколько интересных вещей, с которыми можно [CallerMemberName].
Брайан Уоттс
Не говоря уже о преимуществах регистрации (отражения) в WPF и CLR при использовании модели DO / DP по сравнению с POCO.
tpartee
16

INotifyPropertyChanged при использовании также дает вам возможность добавлять больше логики в код ваших получателей и установщиков ваших свойств.

DependencyProperty пример:

public static DependencyProperty NameProperty = DependencyProperty.Register( "Name", typeof( String), typeof( Customer ) );

public String Name
{
    set { SetValue( NameProperty, value ); }
    get { return ( String ) GetValue( NameProperty ); }
}

В ваших методах получения и установки --- все, что вы можете сделать, это просто вызвать SetValue и GetValue, соответственно, b / c, в других частях платформы getter / setter не вызывается, вместо этого он напрямую вызывает SetValue, GetValue, так что логика вашего свойства не будет надежно исполняться.

С INotifyPropertyChanged, определить событие:

public event PropertyChangedEventHandler PropertyChanged;

А затем просто используйте любую логику в вашем коде, затем вызовите:

// ...
// Something cool...
// ...

if( this.PropertyChanged != null )
{
    PropertyChanged( this, new PropertyChangedEventArgs( "Name" ) );
}

// More cool stuff that will reliably happen...

Это может быть в методе получения / установки или где-либо еще.

Адам
источник
11
Вы также можете получать уведомления об изменениях от DependencyProperties. См. PropertyMetadata.PropertyChangedCallback. Пример по адресу: msdn.microsoft.com/en-us/library/ms745795.aspx
Джо Уайт
2
Кроме того, вы можете вызывать SetValue из любого места, а не только изнутри объекта
aL3891
Это вводит в заблуждение и не соответствует действительности - существует несколько способов подключиться к событиям изменения в DP, даже если они изменены «внутренне». Один из них был указан выше Джо Уайтом
участником
16

Свойства зависимостей предназначены для поддержки привязки (в качестве цели) к элементам пользовательского интерфейса, а не к привязке источника к данным, вот где INotifyProperty приходит. С чистой точки зрения, вы не должны использовать DP в ViewModels.

«Чтобы быть источником привязки, свойство не обязательно должно быть свойством зависимости; вы можете использовать любое свойство CLR в качестве источника привязки. Однако для того, чтобы стать целью привязки, свойство должно быть свойство зависимости. Для того чтобы односторонняя или двусторонняя привязка была эффективной, свойство источника должно поддерживать уведомления об изменениях, которые распространяются на систему привязки и, следовательно, на цель. Для пользовательских источников привязки CLR это означает, что свойство должно поддерживать INotifyPropertyChanged. Коллекции должны поддерживать INotifyCollectionChanged. "

Все объекты зависимостей не могут быть сериализованы (это может затруднить использование ViewModels и DTO (POCO).

Есть различия между DP в Silverlight по сравнению с WPF.

http://msdn.microsoft.com/en-us/library/cc221408(v=VS.95).aspx

http://msdn.microsoft.com/en-us/library/cc903933(VS.95).aspx

Ник Херманс
источник
Я использую сериализованные объекты зависимости с 2009 года без проблем, поэтому не уверен, о чем вы говорите, когда говорите «Все объекты зависимости не могут быть сериализованы» - да, они могут. На самом деле есть много вариантов: codeproject.com/Articles/61440/… emphess.net/2008/11/25/dependencyproperty-serialization И один из моих личных фаворитов: просто предоставлять резервные хранилища для всех ваших DP и делать их сериализуемыми ( Хорошие простые примеры не были легко доступны за 2 минуты поиска в Google, но, уверяю вас, это работает).
tpartee
7

Я тоже должен был рассмотреть это решение недавно.

Я обнаружил, что механизм INotifyPropertyChanged лучше соответствует моим потребностям, потому что он позволяет мне приклеивать мой графический интерфейс к существующей инфраструктуре бизнес-логики без дублирования состояния. У структуры, которую я использовал, была своя собственная схема наблюдателя, и было легко перенаправить один уровень уведомления на следующий. У меня просто был класс, который реализовал интерфейс наблюдателя из моей бизнес-логики и интерфейс INotifyPropertyChanged.

С DP вы не можете определить бэкэнд, который хранит состояние самостоятельно. Мне пришлось бы разрешить .net кешировать копию каждого элемента состояния, к которому я привязан. Это казалось ненужными накладными расходами - мое состояние большое и сложное.

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

Тем не менее, когда мне был нужен пользовательский виджет GUI для предоставления свойства и для того, чтобы изменения этого свойства влияли на другие виджеты GUI, DP доказал простое решение.

Так что я нашел DP полезным для GUI для уведомления GUI.

morechilli
источник
6

Это действительно хорошая идея, чтобы дать зависимости ViewModel для WPF?

.NET 4.0 будет иметь System.Xaml.dll, поэтому вам не придется использовать зависимость от произвольной среды для ее использования. Посмотрите пост Роба Рельеа о его сессии PDC.

Мой дубль

XAML - это язык для описания объектов, а WPF - это фреймворк, чьи описанные объекты являются элементами пользовательского интерфейса.

Их отношения похожи на C #, язык для описания логики, и .NET, каркас, который реализует определенные виды логики.

Цель XAML - декларативные графы объектов. Технологии W * F являются отличными кандидатами на эту парадигму, но XAML существует независимо от них.

XAML и вся система зависимостей были реализованы в виде отдельных стеков для WF и WPF, вероятно, для использования опыта различных команд без создания зависимости (без каламбура) между ними.

Брайан Уоттс
источник
Отвечая, вы, похоже, делаете предположение, что bitbonk считает XAML и WPF одинаковыми. ViewModels должно иметь как можно меньше зависимостей WPF, чтобы не увеличивать логическое разделение, а уменьшать сложность кода и избегать всех проблем, связанных с простым написанием логики в коде позади пользовательского элемента управления. Вы неизбежно будете реализовывать концепции WPF, такие как ICommand, и представлять поведение, которое будет легко обернуть только WPF / Silverlight - единственными проблемами представления потоков в модели представления должны быть CollectionViews и ObservableCollection.
Гусдор
6

Свойства зависимостей являются связующим звеном создания пользовательских элементов управления. Если вы заинтересованы в использовании Intelli-sense для отображения своих свойств в окне свойств во время разработки XAML, вы должны использовать свойства зависимости. INPC никогда не будет показывать свойство в окне свойств во время разработки.

Джон Питерс
источник
4

Кажется, что свойства зависимости должны использоваться в создаваемых вами элементах управления, таких как кнопки. Чтобы использовать свойства в XAML и использовать все функции WPF, эти свойства должны иметь свойства зависимости.

Тем не менее, ваша ViewModel лучше использовать INotifyPropertyChanged. Использование INotifyPropertyChanged даст вам возможность иметь логику получения / установки, если вам нужно.

Я рекомендую проверить версию базового класса Джоша Смита для ViewModel, которая уже реализует INotifyPropertyChanged:

http://joshsmithonwpf.wordpress.com/2007/08/29/a-base-class-which-implements-inotifypropertychanged/

Я думаю, что это отличный пример того, как сделать ViewModel.

timothymcgrath
источник
4

Я думаю, что DependencyProperty и INotifyPropertyChanged используются для двух разных вещей в Binding: первый для включения свойства быть целью привязки и получения ввода от другого свойства (используйте {Binding ...} для установки свойства), последний когда вы хотите, чтобы значение свойства использовалось в качестве источника привязки (имя в выражении пути привязки). Так что выбор чисто технический.

Domnik
источник
2
INotifyPropertyChanged может использоваться в любом случае. Вы можете привязать TwoWay к нему. DependencyProperty требуется по техническим причинам только для некоторых действий, выполняемых над объектом View (например, установка некоторых свойств при создании экземпляра объекта View в XAML). DependencyProperty никогда не требуется для ViewModel.
Oillio
3

Я предпочитаю более прямой подход, о котором я писал в разделе «Модель представления без INotifyPropertyChanged» . Используя альтернативу привязке данных, вы можете привязать напрямую к свойствам CLR без какого-либо бухгалтерского кода. Вы просто пишете старый код .NET в своей модели представления, и он обновляется при изменении вашей модели данных.

Майкл Л Перри
источник
Без INotifyPropertyChanged, PropertyDescriptorиспользуются, что вызывает утечки памяти
Tilak
Библиотека управления обновлениями, которую я представляю в этом посте, использует слабые ссылки, а не дескрипторы свойств. Это не утечка памяти.
Майкл Л Перри
1
Майкл, твоя библиотека генерирует много кода. Я не вижу преимуществ. Я могу добиться того же, генерируя обертку модели с помощью сгенерированных вызовов событий PropertyChanged.
Der_Meister
3

Есть только одна причина, почему предпочтение DependencyObject- Binding будет работать лучше. Просто попробуйте пример с ListBoxи TextBox, заполните список данными из INotifyPropertyChangedсвойства vs. DependencyPropertyи отредактируйте текущий элемент из TextBox...

Ramos
источник
1
Пример кода, пожалуйста
Хасан Тарек
1

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

СПР
источник