订阅嵌套(子)对象的INotifyPropertyChanged

我正在寻找一个干净而优雅的解决方案来处理嵌套(子)对象的INotifyPropertyChanged事件。 示例代码:

 public class Person : INotifyPropertyChanged { private string _firstName; private int _age; private Person _bestFriend; public string FirstName { get { return _firstName; } set { // Short implementation for simplicity reasons _firstName = value; RaisePropertyChanged("FirstName"); } } public int Age { get { return _age; } set { // Short implementation for simplicity reasons _age = value; RaisePropertyChanged("Age"); } } public Person BestFriend { get { return _bestFriend; } set { // - Unsubscribe from _bestFriend's INotifyPropertyChanged Event // if not null _bestFriend = value; RaisePropertyChanged("BestFriend"); // - Subscribe to _bestFriend's INotifyPropertyChanged Event if not null // - When _bestFriend's INotifyPropertyChanged Event is fired, i'd like // to have the RaisePropertyChanged("BestFriend") method invoked // - Also, I guess some kind of *weak* event handler is required // if a Person instance i beeing destroyed } } // **INotifyPropertyChanged implementation** // Implementation of RaisePropertyChanged method } 

专注于BestFriend Property及其价值BestFriend者。 现在我知道我可以手动执行此操作 ,实现注释中描述的所有步骤。 但这将是很多代码,特别是当我计划有许多子属性实现这样的INotifyPropertyChanged 。 当然它们不会总是相同的类型,它们唯一的共同点是INotifyPropertyChanged接口。

原因是,在我的真实场景中,我有一个复杂的“Item”(在购物车中)对象,它在多个层上具有嵌套对象属性(Item有一个“License”对象,它本身可以再次有子对象)和我需要获得有关“项目”的任何单一更改的通知,以便能够重新计算价格。

你有什么好的建议甚至是一些实施来帮助我解决这个问题吗?

不幸的是,我无法/允许使用像PostSharp这样的后期构建步骤来实现我的目标。

非常感谢你提前,
– 托马斯

由于我无法找到现成的解决方案,我已经完成了基于Pieters(和Marks)建议的自定义实现(谢谢!)。

使用这些类,您将收到有关深层对象树中任何更改的通知,这适用于任何实现类型的INotifyPropertyChanged和实现集合的INotifyCollectionChanged *(显然,我正在使用ObservableCollection )。

我希望这是一个非常干净和优雅的解决方案,它虽然没有经过充分测试,但仍有增强空间。 它非常容易使用,只需使用它的静态Create方法创建一个ChangeListener实例并传递INotifyPropertyChanged

 var listener = ChangeListener.Create(myViewModel); listener.PropertyChanged += new PropertyChangedEventHandler(listener_PropertyChanged); 

PropertyChangedEventArgs提供了一个PropertyName ,它始终是对象的完整“路径”。 例如,如果您更改人员的“BestFriend”名称,则PropertyName将为“BestFriend.Name”,如果BestFriend具有BestFriend并且您将其更改为Age,则该值将为“BestFriend.Children []。Age”等等。 不要忘记在对象被销毁时Dispose ,然后它(希望)完全取消订阅所有事件监听器。

它在.NET(测试4)和Silverlight(测试4)中编译。 因为代码分为三个类,我已将代码发布到gist 705450 ,您可以在其中全部获取: https : //gist.github.com/705450 **

*)代码工作的一个原因是ObservableCollection也实现了INotifyPropertyChanged ,否则它将无法正常工作,这是一个众所周知的警告

**)免费使用,根据MIT许可证发布

我认为你正在寻找的东西就像WPF绑定。

INotifyPropertyChanged如何工作是RaisePropertyChanged("BestFriend"); 只有在属性BestFriend更改时BestFriend 。 当对象本身的任何内容发生变化时。

如何实现这一点是通过两步INotifyPropertyChanged事件处理程序。 您的听众将注册更改的Person事件。 设置/更改BestFriend ,您可以注册BestFriend Person的已更改事件。 然后,您开始侦听该对象的已更改事件。

这正是WPF绑定实现这一点的方式。 通过该系统监听嵌套对象的更改。

当你在Person实现它时,它不起作用的原因是级别可以变得非常深,而且BestFriend的更改事件不再具有任何意义(“已经改变了什么?”)。 当你有循环关系时,这个问题变得更大,例如你的最好的朋友是你最好的恶魔的母亲。 然后,当其中一个属性发生更改时,会出现堆栈溢出。

那么,如何解决这个问题就是创建一个可以构建监听器的类。 例如,您可以在BestFriend.FirstName上构建一个侦听BestFriend.FirstName 。 然后,该类将在已更改的Person事件上放置一个事件处理程序,并在BestFriend上侦听更改。 然后,当它发生变化时,它会在BestFriend上放置一个监听BestFriend并监听FirstName变化。 然后,当它发生变化时,它会发送一个事件然后你就可以听了。 这基本上是WPF绑定的工作原理。

有关WPF绑定的更多信息,请参见http://msdn.microsoft.com/en-us/library/ms750413.aspx 。

有趣的解决方案Thomas。

我发现了另一个解决方 它被称为传播者设计模式。 您可以在网上找到更多信息(例如,在CodeProject: C#中的Propagator – 观察者设计模式的替代品 )。

基本上,它是用于更新依赖关系网络中的对象的模式。 当需要通过对象网络推送状态更改时,它非常有用。 状态变化由物体本身表示,物体通过传播者网络传播。 通过将状态更改封装为对象,传播器变得松散耦合。

可重用的Propagator类的类图:

可重用的Propagator类的类图

阅读CodeProject的更多信息。

我写了一个简单的帮手来做到这一点。 您只需在父视图模型中调用BubblePropertyChanged(x => x.BestFriend)。 nb假设您的父级中有一个名为NotifyPropertyChagned的方法,但您可以对其进行调整。

  ///  /// Bubbles up property changed events from a child viewmodel that implements {INotifyPropertyChanged} to the parent keeping /// the naming hierarchy in place. /// This is useful for nested view models. ///  /// Child property that is a viewmodel implementing INotifyPropertyChanged. ///  public IDisposable BubblePropertyChanged(Expression> property) { // This step is relatively expensive but only called once during setup. MemberExpression body = (MemberExpression)property.Body; var prefix = body.Member.Name + "."; INotifyPropertyChanged child = property.Compile().Invoke(); PropertyChangedEventHandler handler = (sender, e) => { this.NotifyPropertyChanged(prefix + e.PropertyName); }; child.PropertyChanged += handler; return Disposable.Create(() => { child.PropertyChanged -= handler; }); } 

在CodeProject上查看我的解决方案: http : //www.codeproject.com/Articles/775831/INotifyPropertyChanged-propagator它完全符合您的需要 – 在此嵌套或任何嵌套中的相关依赖项时,有助于传播(以优雅方式)依赖属性查看模型更改:

 public decimal ExchTotalPrice { get { RaiseMeWhen(this, has => has.Changed(_ => _.TotalPrice)); RaiseMeWhen(ExchangeRate, has => has.Changed(_ => _.Rate)); return TotalPrice * ExchangeRate.Rate; } } 

我已经在网上搜索了一天,我找到了另一个来自Sacha Barber的好解决方案:

http://www.codeproject.com/Articles/166530/A-Chained-Property-Observer

他在Chained Property Observer中创建了弱引用。 如果您想看到另一种实现此function的好方法,请查看文章。

我还想提及Reactive Extensions的一个很好的实现@ http://www.rowanbeach.com/rowan-beach-blog/a-system-reactive-property-change-observer/

此解决方案仅适用于一个观察者级别,而不是完整的观察者链。