为嵌套(子)对象订阅INotifyPropertyChanged

时间:2010-11-10 09:56:52

标签: c# .net events 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属性及其值设置器。现在我知道我可以手动执行此操作,实现评论中描述的所有步骤。但这将是很多代码,特别是当我计划有许多像这样实现INotifyPropertyChanged的子属性时。当然,它们并不总是相同的类型,它们唯一的共同点是INotifyPropertyChanged接口。

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

你有一些好的建议,甚至一些 实施帮我解决 此吗

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

非常感谢您提前,
- 托马斯

6 个答案:

答案 0 :(得分:21)

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

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

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

INotifyPropertyChanged

var listener = ChangeListener.Create(myViewModel); listener.PropertyChanged += new PropertyChangedEventHandler(listener_PropertyChanged); 提供PropertyChangedEventArgs,它始终是对象的完整“路径”。例如,如果您更改人员的“BestFriend”名称,PropertyName将为“BestFriend.Name”,如果PropertyName有一个子集合并且您更改了它的年龄,则值将为“ BestFriend.Children []。年龄“等等。当您的对象被销毁时,不要忘记BestFriend,然后它(希望)完全取消订阅所有事件监听器。

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

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

**)免费使用,在MIT License下发布

答案 1 :(得分:16)

我认为您正在寻找的是WPF绑定。

INotifyPropertyChanged的工作原理是,当RaisePropertyChanged("BestFriend");属性发生变化时,BestFriend必须。当对象本身发生任何变化时都不会。

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

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

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

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

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

答案 2 :(得分:3)

有趣的解决方案托马斯。

我找到了另一个解决方案。它被称为传播者设计模式。您可以在网上找到更多信息(例如,在CodeProject上:Propagator in C# - An Alternative to the Observer Design Pattern)。

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

可重复使用的Propagator类的类图:

A class diagram of the re-usable Propagator classes

详细了解CodeProject

答案 3 :(得分:0)

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

        /// <summary>
    /// 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. 
    /// </summary>
    /// <param name="property">Child property that is a viewmodel implementing INotifyPropertyChanged.</param>
    /// <returns></returns>
    public IDisposable BubblePropertyChanged(Expression<Func<INotifyPropertyChanged>> 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; });
    }

答案 4 :(得分:0)

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

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

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

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

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

答案 5 :(得分:0)

在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;
    }
}