INotifyPropertyChanged vs DependencyProperty dans ViewModel

353

Lors de l'implémentation du ViewModel dans une application WPF d'architecture Model-View-ViewModel, il semble y avoir deux choix principaux pour le rendre lié aux données. J'ai vu des implémentations qui utilisent DependencyPropertydes propriétés contre lesquelles View va se lier et j'ai vu l'implémentation de ViewModel à la INotifyPropertyChangedplace.

Ma question est quand dois-je préférer l'un à l'autre? Y a-t-il des différences de performances? Est-ce vraiment une bonne idée de donner les dépendances de ViewModel à WPF? De quoi d'autre dois-je tenir compte lors de la prise de décision de conception?

bitbonk
la source
11
voir stackoverflow.com/questions/1329138/… pour une façon vérifiée par le compilateur d'implémenter INotifyPropertyChanged. Éviter d'avoir les noms de propriété comme une chaîne magique.
Ian Ringrose
10
En règle générale, il existe une différence majeure entre une propriété de dépendance et une propriété normale dans une classe qui implémente INotifyPropertyChanged. Les propriétés de dépendance peuvent être source ou cible dans la liaison de données, mais les propriétés normales avec prise en charge INotifyPropertyChanged peuvent être utilisées uniquement comme source. Ces solutions ne sont donc pas entièrement interchangeables. L'infrastructure de liaison de données nécessite un DP comme cible pour fonctionner, mais la source peut être soit une propriété normale avec le support INotifyPropertyChanged ou un DP commun.
Mostafa Rezaei
4
Voir stackoverflow.com/a/10595688/200442 pour le mode d'implémentation .net 4.5 INotifyPropertyChanged.
Daniel Little
mieux expliqué ici stackoverflow.com/a/3552550/366064
Bizhan

Réponses:

214

Kent a écrit un blog intéressant sur ce sujet: Afficher les modèles: POCO versus DependencyObjects .

Court résumé:

  1. DependencyObjects ne sont pas marqués comme sérialisables
  2. La classe DependencyObject remplace et scelle les méthodes Equals () et GetHashCode ()
  3. Un DependencyObject a une affinité de thread - il n'est accessible que sur le thread sur lequel il a été créé

Je préfère l'approche POCO. Une classe de base pour PresentationModel (alias ViewModel) qui implémente l'interface INotifyPropertyChanged peut être trouvée ici: http://compositeextensions.codeplex.com

jbe
la source
24
DependencyObject prend également une dépendance sur les bibliothèques WPF, contrairement à POCO, permettant à vos modèles de vue de piloter une autre pile d'interface utilisateur où WPF n'est pas disponible (Compact Framework, Mono).
codekaizen
26
Il est alors clair que les propriétés de dépendance sont uniquement conçues pour l'interface utilisateur et non pour la couche métier.
Andrei Rînea
11
Les propriétés de dépendance nécessitent également un parent DependencyObject. Votre ViewModel ne devrait vraiment pas hériter de DependencyObject.
Gusdor
38

Selon le guide de performance WPF, les DependencyObjects fonctionnent définitivement mieux que les POCO qui implémentent INotifyPropertyChanged:

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

James Ashley
la source
1
Je dois être d'accord sur celui-là ;-): blog.lexique-du-net.com/index.php?post/2010/02/24/…
Jonatha ANTOINE
Si vous sélectionnez .NET Framework version 4, le lien fonctionne toujours. Il n'est tout simplement pas disponible pour la "version actuelle".
doubleYou
Merci d'avoir souligné cela, il y a beaucoup de désinformation scandaleuse sur les développeurs faisant des affirmations salaces selon lesquelles INotifyPropertyChanged est plus rapide ou entraîne moins de frais généraux que les PDD et c'est tout simplement sans fondement. Les DP sont des moyens rapides, élégants et puissants de définir structurellement l'arborescence (de données) virtuelle.
tpartee
Il y a un mal caché dans les DependencyObjects. Ils doivent être créés sur le même thread que les contrôles qui leur sont liés. Cela signifie thread GUI. Cela signifie que vous devez envoyer la création à ce thread. Vous ne pouvez pas avoir ces choses chargées et créées sur un thread d'arrière-plan de DB par exemple. Sauf si vous expédiez la création. Insensé.
ed22
28

Le choix est totalement basé sur votre logique métier et le niveau d'abstraction de l'interface utilisateur. Si vous ne voulez pas une bonne séparation, DP travaillera pour vous.

DependencyProperties sera applicable principalement au niveau VisualElements, donc ce ne sera pas une bonne idée si nous créons beaucoup de DP pour chacune de nos exigences commerciales. En outre, il existe un coût plus élevé pour DP qu'un INotifyPropertyChanged. Lorsque vous concevez un WPF / Silverlight, essayez de concevoir l'interface utilisateur et ViewModel de manière totalement distincte afin qu'à tout moment nous puissions modifier les contrôles de disposition et d'interface utilisateur (en fonction du thème et des styles)

Reportez-vous également à cet article - /programming/275098/what-applications-could-i-study-to-understand-datamodel-view-viewmodel . Le lien fait beaucoup référence au modèle Model-View-ViewModel, qui est très pertinent pour cette discussion.

Jobi Joy
la source
9
Le post de jbe répond plus précisément aux différences. Le fait qu'une machine virtuelle (ou un présentateur) hérite de DependencyObject ne signifie pas qu'il ne peut pas être stylisé ou n'est pas séparé logiquement de la vue, cela signifie simplement que le stockage des valeurs de propriété est différent des champs déclarés explicitement dans le Style POCO. Cela étant dit, la sérialisation, l'égalité logique et l'affinité de thread sont des problèmes réels auxquels les machines virtuelles basées sur DepedencyObject doivent faire face.
micahtan
"Il y a aussi un coût plus élevé pour DP qu'un INotifyPropertyChanged" - où est votre source de preuve à ce sujet? Beaucoup de développeurs font cette affirmation sans aucune preuve à l'appui. Selon MSDN, ce n'est pas vrai. "essayez de concevoir l'interface utilisateur et ViewModel de manière totalement distincte afin qu'à tout moment nous puissions modifier les contrôles de disposition et d'interface utilisateur" - encore une fois, cela n'a absolument rien à voir avec POCO + PropChange par rapport à DO / DP. Si quoi que ce soit, le registre Reflection and Path dans DO / DP améliore votre capacité à travailler du côté visuel.
tpartee
20

Du point de vue de l'expressivité, j'aime beaucoup utiliser les propriétés de dépendance et grincer des dents à l'idée INotifyPropertyChanged. Outre les stringnoms de propriété et les éventuelles fuites de mémoire dues à l'abonnement aux événements, il INotifyPropertyChangedexiste un mécanisme beaucoup plus explicite.

Les propriétés de dépendance impliquent "quand ceci, faites cela" en utilisant des métadonnées statiques faciles à comprendre. C'est une approche déclarative qui obtient mon vote pour l'élégance.

Bryan Watts
la source
1
La partie chaîne a maintenant une solution avec l'opérateur nameof.
Newtopian
@Newtopian: Vrai. Il y a aussi des choses intéressantes possibles avec [CallerMemberName].
Bryan Watts
Sans parler de la richesse des avantages de l'enregistrement des propriétés (réflexion) dans WPF et CLR lors de l'utilisation d'un modèle DO / DP par rapport à un POCO.
tpartee
16

INotifyPropertyChanged lorsqu'il est utilisé vous donne également la possibilité d'ajouter plus de logique dans le code de vos getters et setter de vos propriétés.

DependencyProperty exemple:

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

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

Dans votre getter et setter --- tout ce que vous pouvez faire est simplement d'appeler SetValue et GetValue respectivement, b / c dans d'autres parties du framework, le getter / setter n'est pas appelé, au lieu de cela il appelle directement SetValue, GetValue, donc votre logique de propriété ne serait pas être exécuté de manière fiable.

Avec INotifyPropertyChanged, définissez un événement:

public event PropertyChangedEventHandler PropertyChanged;

Et puis simplement avoir n'importe quelle logique n'importe où dans votre code, puis appelez:

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

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

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

Cela pourrait être dans un getter / setter, ou n'importe où ailleurs.

Adam
la source
11
Vous pouvez également obtenir des notifications de modification de DependencyProperties. Voir PropertyMetadata.PropertyChangedCallback. Exemple sur: msdn.microsoft.com/en-us/library/ms745795.aspx
Joe White
2
En outre, vous pouvez également appeler SetValue de n'importe où, pas seulement de l'intérieur de la propriété
aL3891
Ceci est trompeur et faux - il existe plusieurs façons de se connecter à des événements de modification sur un DP, même lorsqu'il est modifié «en interne». L'un d'eux a été souligné ci-dessus par Joe White
tpartee
16

Les propriétés de dépendance sont destinées à prendre en charge la liaison (en tant que cible) sur les éléments d'interface utilisateur et non en tant que source à la liaison de données, c'est là qu'INotifyProperty entre en jeu. D'un point de vue pur, vous ne devriez pas utiliser DP sur un ViewModels.

"Pour être la source d'une liaison, une propriété n'a pas besoin d'être une propriété de dépendance. Vous pouvez utiliser n'importe quelle propriété CLR comme source de liaison. Cependant, pour être la cible d'une liaison, la propriété doit être un propriété de dépendance. Pour qu'une liaison unidirectionnelle ou bidirectionnelle soit efficace, la propriété source doit prendre en charge les notifications de modification qui se propagent au système de liaison et donc à la cible. Pour les sources de liaison CLR personnalisées, cela signifie que la propriété doit prendre en charge INotifyPropertyChanged. Les collections doivent prendre en charge INotifyCollectionChanged. "

Tous les objets de dépendance ne peuvent pas être sérialisés (cela pourrait entraver l'utilisation des ViewModels et des DTO (POCO)).

Il existe des différences entre DP dans Silverlight et WPF.

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

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

Nick Hermans
la source
J'utilise des objets de dépendance sérialisés depuis 2009 sans problème, donc je ne sais pas de quoi vous parlez lorsque vous dites "Tous les objets de dépendance ne peuvent pas être sérialisés" - oui, ils le peuvent. En fait , il existe de nombreuses options: codeproject.com/Articles/61440/... emphess.net/2008/11/25/dependencyproperty-serialization et un de mes coups de cœur: il suffit de fournir les magasins d'accompagnement pour tous vos partenaires au développement et de faire les sérialisable ( aucun bon exemple simple n'était facilement disponible en 2 minutes de recherche sur Google, mais je vous assure que cela fonctionne).
tpartee
7

J'ai moi aussi dû reconsidérer cette décision récemment.

J'ai trouvé que le mécanisme INotifyPropertyChanged convenait mieux à mes besoins car il me permettait de coller mon interface graphique à un cadre logique métier existant sans dupliquer l'état. Le cadre que j'utilisais avait son propre modèle d'observateur et il était facile de transmettre un niveau de notification au suivant. J'ai simplement eu une classe qui a implémenté l'interface d'observateur à partir de mon cadre logique métier et de l'interface INotifyPropertyChanged.

Avec DP, vous ne pouvez pas définir vous-même le backend qui stocke l'état. J'aurais dû laisser .net mettre en cache une copie de chaque état auquel je me liais. Cela semblait être une surcharge inutile - mon état est vaste et compliqué.

J'ai donc trouvé INotifyPropertyChanged mieux pour exposer les propriétés de la logique métier à l'interface graphique.

Cela étant dit, là où j'avais besoin d'un widget GUI personnalisé pour exposer une propriété et pour que les modifications apportées à cette propriété affectent d'autres widgets GUI, DP a prouvé la solution simple.

J'ai donc trouvé DP utile pour la notification GUI vers GUI.

morechilli
la source
6

Est-ce vraiment une bonne idée de donner les dépendances de ViewModel à WPF?

.NET 4.0 aura System.Xaml.dll, vous n'aurez donc pas à dépendre d'une infrastructure arbitraire pour l'utiliser. Voir le post de Rob Relyea sur sa session PDC.

Ma prise

XAML est un langage pour décrire des objets et WPF est un framework dont les objets décrits sont des éléments d'interface utilisateur.

Leur relation est similaire à C #, un langage pour décrire la logique, et .NET, un cadre qui implémente des types particuliers de logique.

Le but de XAML est les graphes d'objets déclaratifs. Les technologies W * F sont d'excellents candidats pour ce paradigme, mais XAML existe indépendamment d'eux.

XAML et l'ensemble du système de dépendance ont été mis en œuvre en tant que piles distinctes pour WF et WPF, probablement pour tirer parti de l'expérience de différentes équipes sans créer de dépendance (sans jeu de mots) entre elles.

Bryan Watts
la source
En répondant, vous semblez faire l'hypothèse que bitbonk considère que XAML et WPF sont identiques. ViewModels doit avoir le moins de dépendances WPF possible, non pour augmenter la séparation logique, mais pour diminuer la complexité du code et éviter tous les problèmes associés à la simple écriture de la logique dans le code-behind d'un contrôle utilisateur. Vous implémenterez inévitablement des concepts WPF comme ICommand et présenterez un comportement que seul WPF / Silverlight pourra envelopper facilement - vos seules préoccupations de filetage de présentation dans un modèle de vue devraient être CollectionViews et ObservableCollection.
Gusdor
6

Les propriétés de dépendance sont le ciment de la création de contrôles personnalisés. Si vous souhaitez utiliser Intelli-sense pour afficher vos propriétés dans la fenêtre des propriétés au moment du design XAML, vous devez utiliser les propriétés de dépendance. INPC n'affichera jamais une propriété dans la fenêtre de propriétés au moment du design.

John Peters
la source
4

Il semble que les propriétés de dépendance doivent être utilisées dans les contrôles que vous créez, tels que les boutons. Pour utiliser des propriétés dans XAML et utiliser toutes les fonctionnalités WPF, ces propriétés doivent avoir des propriétés de dépendance.

Cependant, votre ViewModel est préférable d'utiliser INotifyPropertyChanged. L'utilisation d'INotifyPropertyChanged vous donnera la possibilité d'avoir une logique getter / setter si vous en avez besoin.

Je recommande de vérifier la version de Josh Smith d'une classe de base pour un ViewModel qui implémente déjà INotifyPropertyChanged:

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

Je pense que c'est un excellent exemple de la façon de faire un ViewModel.

timothymcgrath
la source
4

Je pense que DependencyProperty et INotifyPropertyChanged sont utilisés pour deux choses différentes dans Binding: la première pour permettre à une propriété d'être la cible d'une liaison et de recevoir l'entrée d'une autre propriété (utilisez {Binding ...} pour définir la propriété), la dernière lorsque vous souhaitez que la valeur d'une propriété soit utilisée comme source d'une liaison (nom dans l'expression du chemin de liaison). Le choix est donc purement technique.

Domnik
la source
2
Un INotifyPropertyChanged peut être utilisé dans les deux cas. Vous pouvez y associer TwoWay. Un DependencyProperty est requis pour des raisons techniques uniquement pour certaines actions effectuées sur un objet View (définition de certaines propriétés lors de l'instanciation d'un objet View en XAML, par exemple). Un DependencyProperty n'est jamais requis pour un ViewModel.
oillio
3

Je préfère une approche plus directe, sur laquelle j'ai blogué dans Presentation Model Without INotifyPropertyChanged . En utilisant une alternative à la liaison de données, vous pouvez vous lier directement aux propriétés CLR sans aucun code de tenue de livres. Vous écrivez simplement du code .NET ancien dans votre modèle d'affichage, et il est mis à jour lorsque votre modèle de données change.

Michael L Perry
la source
Sans INotifyPropertyChanged, PropertyDescriptorsont utilisés, ce qui provoque des fuites de mémoire
Tilak
La bibliothèque de contrôles de mise à jour que je présente dans ce billet de blog utilise des références faibles, pas des descripteurs de propriété. Il ne fuit pas la mémoire.
Michael L Perry
1
Michael, votre bibliothèque génère beaucoup de code. Je ne vois aucun avantage. Je peux obtenir le même résultat en générant un wrapper de modèle avec des appels d'événement PropertyChanged générés.
Der_Meister
3

Il n'y a qu'une seule raison pour laquelle préférer un DependencyObject- La reliure fonctionnera mieux. Essayez simplement un exemple avec un ListBoxet TextBox, remplissez la liste avec les données de la INotifyPropertyChangedpropriété DependencyPropertyet modifiez l'élément actuel à partir de TextBox...

ramos
la source
1
Échantillon de code, s'il vous plaît
Hassan Tareq
1

Si vous souhaitez exposer des propriétés à d'autres contrôles, vous devez utiliser les propriétés de dépendance ... Mais bonne chance car elles prennent un certain temps à comprendre ...

JWP
la source