ViewModel 中的 INotifyPropertyChanged 与 DependencyProperty
Posted
技术标签:
【中文标题】ViewModel 中的 INotifyPropertyChanged 与 DependencyProperty【英文标题】:INotifyPropertyChanged vs. DependencyProperty in ViewModel 【发布时间】:2010-09-22 10:25:50 【问题描述】:在 Model-View-ViewModel 架构 WPF 应用程序中实现 ViewModel 时,似乎有两个主要选择如何使其可数据绑定。我已经看到使用DependencyProperty
作为 View 将要绑定的属性的实现,并且我已经看到 ViewModel 实现了 INotifyPropertyChanged
。
我的问题是我什么时候应该更喜欢一个而不是另一个?有性能差异吗?将 ViewModel 依赖项提供给 WPF 真的是一个好主意吗?在做出设计决策时我还需要考虑什么?
【问题讨论】:
请参阅***.com/questions/1329138/… 了解编译器检查的实现 INotifyPropertyChanged 的方式。避免将属性名称作为魔术字符串。 在实现 INotifyPropertyChanged 的类中,依赖属性和普通属性之间通常存在主要区别。依赖属性可以是数据绑定中的源或目标,但具有 INotifyPropertyChanged 支持的普通属性只能用作源。所以这些解决方案不能完全互换。数据绑定基础结构需要 DP 作为目标才能工作,但源可能是具有 INotifyPropertyChanged 支持的普通属性或通用 DP。 参见***.com/a/10595688/200442 了解.net 4.5 实现INotifyPropertyChanged
的方式。
最好在这里解释***.com/a/3552550/366064
【参考方案1】:
Kent 写了一篇关于这个主题的有趣博客:View Models: POCOs versus DependencyObjects。
简短总结:
-
DependencyObjects 未标记为
可序列化
DependencyObject 类覆盖并密封了 Equals() 和
GetHashCode() 方法
DependencyObject 具有线程亲和性——它只能被访问
在它所在的线程上
已创建
我更喜欢 POCO 方法。可以在此处找到实现 INotifyPropertyChanged 接口的 PresentationModel(又名 ViewModel)的基类:http://compositeextensions.codeplex.com
【讨论】:
DependencyObject 还依赖于 WPF 库,而 POCO 没有,从而允许您的视图模型驱动其他一些 WPF 不可用的 UI 堆栈(Compact Framework,Mono)。 很明显,依赖属性是专门为 UI 构建的,而不是为业务层构建的。 依赖属性也需要一个 DependencyObject 父级。你的 ViewModel 真的不应该从 DependencyObject 继承。【参考方案2】:依赖属性是自定义控件创建的粘合剂。如果您有兴趣在 XAML 设计时使用 Intelli-sense 在属性窗口中显示您的属性,则必须使用 Dependency 属性。 INPC 永远不会在设计时在属性窗口中显示属性。
【讨论】:
【参考方案3】:只有一件事为什么更喜欢DependencyObject
- 绑定会更好。只需尝试使用ListBox
和TextBox
的示例,使用来自INotifyPropertyChanged
属性与DependencyProperty
的数据填充列表并编辑来自TextBox
的当前项目...
【讨论】:
请提供代码示例【参考方案4】:依赖属性旨在支持 UI 元素上的绑定(作为目标),而不是作为数据绑定的源,这就是 INotifyProperty 的用武之地。从纯粹的角度来看,您不应该在 ViewModel 上使用 DP。
"为了成为绑定的源,属性不需要是依赖属性;您可以使用任何 CLR 属性作为绑定源。但是,为了成为绑定的目标,属性必须是依赖属性。要使单向或双向绑定有效,源属性必须支持传播到绑定系统和目标的更改通知。对于自定义 CLR 绑定源,这意味着该属性必须支持 INotifyPropertyChanged。集合应该支持 INotifyCollectionChanged。"
所有依赖对象都不能被序列化(这可能会妨碍 ViewModel 和 DTO (POCO) 的使用。
与 WPF 相比,Silverlight 中的 DP 存在差异。
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 提供后备存储并使其可序列化(在 Google 上搜索 2 分钟后没有现成的简单示例,但是我向你保证这行得通)。【参考方案5】: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,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...
这可能在 getter/setter 中,或其他任何地方。
【讨论】:
您也可以从 DependencyProperties 获取更改通知。请参阅 PropertyMetadata.PropertyChangedCallback。例如:msdn.microsoft.com/en-us/library/ms745795.aspx 此外,您也可以从任何地方调用 SetValue,而不仅仅是从属性内部 这是误导和不真实的 - 有多种方法可以挂钩 DP 上的更改事件,即使它是“内部”更改的。上面乔·怀特(Joe White)指出了其中之一【参考方案6】:如果你想将属性暴露给其他控件,你必须使用依赖属性......但是祝你好运,因为它们需要一段时间才能弄清楚......
【讨论】:
【参考方案7】:根据 WPF 性能指南,DependencyObjects 的性能肯定优于实现 INotifyPropertyChanged 的 POCO:
http://msdn.microsoft.com/en-us/library/bb613546.aspx
【讨论】:
我必须同意那个 ;-) : blog.lexique-du-net.com/index.php?post/2010/02/24/… 如果您选择 .NET Framework 版本 4,则该链接仍然有效。它只是不适用于“当前版本”。 感谢您指出这一点,那里有很多可耻的错误信息,即开发人员恶意声称 INotifyPropertyChanged 比 DP 更快或产生的开销更少,而且这完全没有根据。 DP 是在结构上定义虚拟(数据)树的快速、优雅和强大的方法。 DependencyObjects 有一个隐藏的邪恶。它们需要在与绑定到它们的控件相同的线程上创建。这意味着 GUI 线程。这意味着您需要将创建分派到该线程。例如,您不能在数据库的某个后台线程上加载和创建这些东西。除非你派遣创作。疯了。【参考方案8】:我认为 DependencyProperty 和 INotifyPropertyChanged 用于 Binding 中的两个不同的事情:第一个用于使属性成为绑定的目标并接收来自另一个属性的输入(使用 Binding ... 设置属性) ,最后一个当您希望将属性的值用作绑定源(绑定路径表达式中的名称)时。 所以选择只是技术性的。
【讨论】:
无论哪种情况都可以使用 INotifyPropertyChanged。您可以将 TwoWay 绑定到它。出于技术原因,仅在对 View 对象执行的某些操作(例如,在 XAML 中实例化 View 对象时设置某些属性)时才需要 DependencyProperty。 ViewModel 从不需要 DependencyProperty。【参考方案9】:选择完全基于您的业务逻辑和 UI 抽象级别。如果你不想要一个好的分离,那么DP会为你工作。
DependencyProperties 将主要适用于 VisualElements 级别,因此如果我们为每个业务需求创建大量 DP,这将不是一个好主意。 DP 的成本也比 INotifyPropertyChanged 高。当您设计 WPF/Silverlight 时,请尝试将 UI 和 ViewModel 完全分开,以便我们可以随时更改布局和 UI 控件(基于主题和样式)
也参考这篇文章 - https://***.com/questions/275098/what-applications-could-i-study-to-understand-datamodel-view-viewmodel 。该链接有很多对Model-View-ViewModel模式的参考,与本次讨论非常相关。
【讨论】:
jbe 的帖子更准确地回答了这些差异。仅仅因为 VM(或 Presenter)从 DependencyObject 继承并不意味着它不能被设置样式或在逻辑上与 View 不分离,它只是意味着属性值的存储不同于在POCO 风格。话虽如此,序列化、逻辑相等和线程亲和性是基于 DepedencyObject 的 VM 必须处理的实际问题。 “另外,DP 的成本比 INotifyPropertyChanged 更高”——你的证据来源在哪里?许多开发人员在没有任何证据支持的情况下提出这一主张。根据 MSDN,这不是真的。 “尝试将 UI 和 ViewModel 设计为完全分开,以便我们可以随时更改布局和 UI 控件”——同样,这与 POCO + PropChange 与 DO/DP 完全无关。如果有的话,DO/DP 中的反射和路径注册表可以提高您在视觉方面工作的能力。【参考方案10】:似乎应该在您创建的控件(例如按钮)中使用依赖属性。要在 XAML 中使用属性并使用所有 WPF 功能,这些属性必须是依赖属性。
但是,您的 ViewModel 最好使用 INotifyPropertyChanged。如果需要,使用 INotifyPropertyChanged 将使您能够拥有 getter/setter 逻辑。
我建议查看 Josh Smith 的基类版本,以了解已经实现 INotifyPropertyChanged 的 ViewModel:
http://joshsmithonwpf.wordpress.com/2007/08/29/a-base-class-which-implements-inotifypropertychanged/
我认为这是一个如何制作 ViewModel 的绝佳示例。
【讨论】:
【参考方案11】:我更喜欢更直接的方法,我在 Presentation Model Without INotifyPropertyChanged 上写过博客。使用数据绑定的替代方法,您可以直接绑定到 CLR 属性,而无需任何簿记代码。您只需在视图模型中编写普通的 .NET 代码,它会在您的数据模型更改时更新。
【讨论】:
不使用INotifyPropertyChanged
,使用PropertyDescriptor
,导致memory leaks
我在该博客文章中介绍的更新控件库使用弱引用,而不是属性描述符。它不会泄漏内存。
Michael,您的库生成了大量代码。我没有看到好处。我可以通过使用生成的 PropertyChanged 事件调用生成模型包装器来实现同样的目的。【参考方案12】:
我最近也不得不考虑这个决定。
我发现 INotifyPropertyChanged 机制更适合我的需求,因为它允许我将我的 GUI 粘合到现有的业务逻辑框架而不复制状态。我使用的框架有自己的观察者模式,很容易将一个级别的通知转发到下一个级别。我只是有一个类,它从我的业务逻辑框架和 INotifyPropertyChanged 接口实现了观察者接口。
使用 DP,您无法自己定义存储状态的后端。我将不得不让.net 缓存我绑定到的每个状态项的副本。这似乎是不必要的开销 - 我的状态很大而且很复杂。
所以在这里我发现 INotifyPropertyChanged 更适合将属性从业务逻辑公开到 GUI。
话虽如此,我需要一个自定义 GUI 小部件来公开属性并更改该属性以影响其他 GUI 小部件 DP 证明是简单的解决方案。
所以我发现 DP 对于 GUI 到 GUI 的通知很有用。
【讨论】:
【参考方案13】:从表现力的角度来看,我非常喜欢使用依赖属性,想到INotifyPropertyChanged
就感到畏缩。除了 string
属性名称和由于事件订阅可能导致的内存泄漏之外,INotifyPropertyChanged
是一种更明确的机制。
依赖属性使用易于理解的静态元数据暗示“当这个,做那个”。这是一种声明式的方法,让我对优雅投了赞成票。
【讨论】:
字符串部分现在有了nameof操作符的解决方案。 @Newtopia:是的。[CallerMemberName]
还可以做一些有趣的事情。
在使用 DO/DP 模型与 POCO 时,更不用说 WPF 和 CLR 中的大量属性注册(反射)优势了。【参考方案14】:
将 ViewModel 依赖项赋予 WPF 真的是个好主意吗?
.NET 4.0 将具有 System.Xaml.dll,因此您不必依赖于任意框架即可使用它。有关他的 PDC 会议,请参阅 Rob Relyea's 帖子。
我的看法
XAML 是一种描述对象的语言,WPF 是一个框架,其描述的对象是 UI 元素。
它们的关系类似于用于描述逻辑的语言 C# 和实现特定类型逻辑的框架 .NET。
XAML 的目的是声明性对象图。 W*F 技术非常适合这种范式,但 XAML 独立于它们而存在。
XAML 和整个依赖系统被实现为 WF 和 WPF 的独立堆栈,可能是为了利用不同团队的经验,而不会在它们之间创建依赖关系(没有双关语)。
【讨论】:
通过回答,您似乎假设 bitbonk 认为 XAML 和 WPF 是相同的。 ViewModel 应该具有尽可能少的 WPF 依赖项,不是为了增加逻辑分离,而是为了降低代码复杂性并避免与简单地在用户控件的代码隐藏中编写逻辑相关的所有问题。您将不可避免地实现 ICommand 之类的 WPF 概念,并呈现只有 WPF / Silverlight 能够轻松包装的行为 - 您在视图模型中唯一的呈现线程关注点应该是 CollectionViews 和 ObservableCollection。以上是关于ViewModel 中的 INotifyPropertyChanged 与 DependencyProperty的主要内容,如果未能解决你的问题,请参考以下文章