在ViewModel中,INotifyPropertyChanged与DependencyProperty的区别

376
在实现WPF应用程序中Model-View-ViewModel (MVVM)体系结构中的ViewModel时,似乎有两种主要选择如何使其可数据绑定。我见过一些实现使用DependencyProperty来绑定View的属性,也见过ViewModel实现INotifyPropertyChanged。 我的问题是什么情况下应该优先考虑其中一种?是否存在性能差异?将依赖项提供给WPF真的是个好主意吗?在做出设计决策时还需要考虑什么?

11
请参见 https://dev59.com/S3M_5IYBdhLWcg3wjj4p#1333874,了解实现 INotifyPropertyChanged 的编译器检查方法。避免属性名称成为魔法字符串。 - Ian Ringrose
12
实现INotifyPropertyChanged接口的类中,依赖属性和普通属性有显著区别。依赖属性可以作为数据绑定的源或目标,而带有INotifyPropertyChanged支持的普通属性只能作为源使用。因此这些解决方案不能完全互换。数据绑定基础设施需要DP作为目标才能工作,但源可以是带有INotifyPropertyChanged支持的普通属性或者普通DP。 - Mostafa Rezaei
4
请参考 https://dev59.com/bHM_5IYBdhLWcg3wlEPO#10595688,了解实现 INotifyPropertyChanged 接口的 .NET 4.5 方法。 - Daniel Little
请参考以下链接中最好的解释 https://dev59.com/U3A65IYBdhLWcg3w8zft#3552550 - Bizhan
14个回答

222

Kent写了一篇关于这个话题的有趣博客:视图模型:POCO与DependencyObject

简短总结:

  1. DependencyObjects未标记为可序列化
  2. DependencyObject类覆盖并封印Equals()和GetHashCode()方法
  3. DependencyObject具有线程亲和性-只能在创建它的线程上访问

我更喜欢POCO方法。可以在此处找到实现INotifyPropertyChanged接口的PresentationModel(又名ViewModel)的基类:http://compositeextensions.codeplex.com


24
DependencyObject还对WPF库有依赖关系,而POCO没有,这使得您的视图模型可以驱动其他UI堆栈,在那里WPF不可用(紧凑框架、Mono)。 - codekaizen
29
很明显,依赖属性仅用于UI而不是业务层。 - Andrei Rînea
11
依赖属性也需要一个 DependencyObject 父级。你的 ViewModel 不应该继承自 DependencyObject。 - Gusdor

42

1
我必须同意这一点 ;-):http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged - Jonathan ANTOINE
如果您选择.NET Framework 4版本,则该链接仍然有效。它只是不适用于“当前版本”。 - doubleYou
感谢您指出这一点,有很多令人震惊的错误信息称开发人员声称INotifyPropertyChanged比DP更快或产生更少的开销,但这是毫无根据的。DP是一种快速、优雅和强大的方式,用于结构化定义虚拟(数据)树。 - tpartee
依赖对象存在一个隐藏的问题。它们需要在绑定到它们的控件所在的线程上创建。也就是GUI线程。这意味着你需要将创建操作调度到该线程上。你不能在后台线程中从数据库等地方加载和创建这些对象,除非你进行调度创建操作。这太疯狂了。 - ed22

29

选择完全基于您的业务逻辑和UI抽象级别。如果你不想要一个很好的分离,那么DP就适合你。

DependencyProperties主要适用于VisualElements级别,因此如果我们为每个业务需求创建大量的DP,则不是一个好主意。此外,DP的成本比INotifyPropertyChanged更高。当您设计WPF / Silverlight时,请尝试完全分离UI和ViewModel,以便在任何时间点都可以更改布局和UI控件(基于主题和样式)。

也可以参考这篇文章 - https://stackoverflow.com/questions/275098/what-applications-could-i-study-to-understand-datamodel-view-viewmodel。该链接包含许多与Model-View-ViewModel模式相关的参考资料,非常与本文讨论相关。


9
jbe的帖子更准确地回答了这些区别。仅仅因为一个VM(或Presenter)继承自DependencyObject并不意味着它不能被样式化或在逻辑上与View分离,只是意味着属性值的存储方式不同于POCO风格中显式声明的字段。话虽如此,基于DepedencyObject的VM面临着序列化、逻辑相等性和线程亲和力等真正的问题。 - micahtan
“此外,DP的成本比INotifyPropertyChanged更高” - 你有什么证据来支持这个说法?很多开发人员都这样声称,但没有任何证据支持。根据MSDN的说法,这是不正确的。“尝试完全分离UI和ViewModel的设计,以便在任何时候我们都可以更改布局和UI控件” - 再次强调,这与POCO + PropChange与DO / DP无关。如果有什么区别,DO / DP中的Reflection和Path注册表可以提高您在视觉方面的工作能力。 - tpartee

20

就表达能力而言,我非常喜欢使用依赖属性,并对使用 INotifyPropertyChanged 感到不满。除了 string 属性名称和由于事件订阅导致的可能的内存泄漏之外,INotifyPropertyChanged 是一个更为明确的机制。

依赖属性使用易于理解的静态元数据来暗示“当此时,执行那个”的关系。这是一种声明式的方法,使它在优雅方面得到了我的支持。


1
字符串部分现在使用 nameof 运算符解决了。 - Newtopian
1
@Newtopian:没错。使用[CallerMemberName]还有一些有趣的事情可以做。 - Bryan Watts
更不用说在使用DO/DP模型与POCO模型时,WPF和CLR中的属性注册(反射)带来的丰富好处了。 - tpartee

19

依赖属性旨在支持UI元素的绑定(作为目标),而不是数据绑定的源。这就是INotifyProperty的作用所在。从纯粹的角度来看,您不应该在ViewModels上使用DP。

“为了成为绑定的源,属性不需要是依赖属性;您可以使用任何CLR属性作为绑定源。但是,为了成为绑定的目标,属性必须是依赖属性。对于单向或双向绑定,源属性必须支持传播到绑定系统并因此传播到目标的更改通知。对于自定义CLR绑定源,这意味着该属性必须支持INotifyPropertyChanged。集合应支持INotifyCollectionChanged。”

所有依赖对象都无法序列化(这可能会阻碍ViewModels和DTO(POCO)的使用)。

Silverlight中的DP与WPF中有所不同。

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

http://msdn.microsoft.com/zh-cn/library/cc903933(VS.95).aspx


自从2009年开始我一直在使用序列化依赖对象,没有遇到任何问题,所以当你说“所有依赖对象都不能被序列化”时,我不确定你在说什么 - 是的,它们可以被序列化。事实上,有许多选项:https://www.codeproject.com/Articles/61440/Serialize-DependencyObject-It-s-easy http://www.emphess.net/2008/11/25/dependencyproperty-serialization/还有我个人最喜欢的方法:只需为所有DP提供后备存储并使其可序列化(在2分钟的谷歌搜索中没有好的简单示例可用,但我向您保证这将起作用)。 - tpartee

15

INotifyPropertyChanged被使用时,还能在属性的getter和setter代码中增加更多的逻辑。

DependencyProperty示例:

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

public String Name
{
    set { SetValue( NameProperty, value ); }
    get { return ( String ) GetValue( NameProperty ); }
}
在你的getter和setter中,你只能简单地分别调用SetValue和GetValue,因为在框架的其他部分中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...

这可以在 getter/setter 或任何其他地方使用。


12
您也可以从DependencyProperties获取更改通知。请参阅PropertyMetadata.PropertyChangedCallback。示例:http://msdn.microsoft.com/en-us/library/ms745795.aspx - Joe White
2
此外,您可以从任何地方调用SetValue,而不仅仅是在属性内部。 - aL3891
这是误导性和不真实的 - 即使在 DP 内部更改时,仍然有多种方法可以钩入更改事件。其中一个被 Joe White 上面指出了。 - tpartee

7

最近我也必须考虑这个决定。

我发现INotifyPropertyChanged机制更适合我的需求,因为它允许我将GUI与现有的业务逻辑框架连接起来,而不会重复状态。我使用的框架具有自己的观察者模式,很容易将一级通知转发到下一级。我只需要一个类来实现我的业务逻辑框架中的观察者接口和INotifyPropertyChanged接口。

使用DP,您无法自定义存储状态的后端。我必须让.NET缓存我要绑定的每个状态项的副本。这似乎是一种不必要的开销——我的状态又大又复杂。

因此,我发现INotifyPropertyChanged更适用于从业务逻辑到GUI公开属性。

尽管如此,在我需要自定义GUI小部件来公开属性并且希望更改该属性以影响其他GUI小部件时,DP被证明是简单的解决方案。

因此,在那里我发现DP对于GUI到GUI通知非常有用。


7

将ViewModel的依赖项提供给WPF真的是一个好主意吗?

.NET 4.0将拥有System.Xaml.dll,因此您不必依赖任意框架来利用它。请参阅Rob Relyea关于他的PDC会话的帖子。

我的看法

XAML是描述对象的语言,而WPF是一个框架,其描述的对象是UI元素。

它们的关系类似于C#,一种描述逻辑的语言和.NET,一个实现特定类型逻辑的框架。

XAML的目的是声明性对象图。W*F技术非常适合这种范式,但XAML独立于它们存在。

XAML和整个依赖系统作为WF和WPF的单独堆栈实现,可能是为了利用不同团队的经验而不创建依赖(无意冒犯)它们之间。


通过回答,您似乎认为bitbonk认为XAML和WPF是相同的。ViewModel应该尽可能少地依赖WPF,不是为了增加逻辑分离,而是为了减少代码复杂性并避免与仅在用户控件的代码后台中编写逻辑相关的所有问题。您将不可避免地实现WPF概念,如ICommand,并呈现仅WPF / Silverlight能够轻松包装的行为 - 您在视图模型中唯一的演示线程关注应该是CollectionViews和ObservableCollection。 - Gusdor

6

依赖属性是自定义控件创建的粘合剂。如果您想在XAML设计时使用智能感知来显示属性窗口中的属性,则必须使用依赖属性。 INPC永远不会在设计时显示属性窗口中的属性。


4

我认为在绑定中,DependencyProperty和INotifyPropertyChanged用于两个不同的目的:第一个是使属性成为绑定的目标并从另一个属性接收输入(使用{Binding ...}设置属性),而最后一个则是当您希望属性的值被用作绑定的源时使用(在绑定路径表达式中使用名称)。因此,选择仅仅是技术上的。


2
一个 INotifyPropertyChanged 接口可以在两种情况下使用。你可以将 TwoWay 绑定到它上面。DependencyProperty 只是出于技术原因,某些操作需要在 View 对象上执行(例如,在 XAML 中实例化 View 对象时设置某些属性)。ViewModel 永远不需要 DependencyProperty。 - oillio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接