65.9K
CodeProject 正在变化。 阅读更多。
Home

NotifyParentObservableCollection (Part 2) 监控其子属性的变化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (10投票s)

2011 年 1 月 23 日

Eclipse

2分钟阅读

viewsIcon

33762

downloadIcon

391

ObservableCollection使用弱事件模式附加到其子项的PropertyChanged事件,并相应地引发ChildPropertyChanged事件。复制BindingList的功能,但没有开销。当您需要更新/选择/排列/移动项目时非常有用。

目录

弱事件

使用强事件处理程序实现,NotifyParentObservableCollection<T>完成了它应该做的事情。但是,我的第一篇文章收到了很多负面反馈。

我的集合本来应该取代BindingList<T>提供的功能(转发其子项的INotifyPropertyChanged事件)。

我被告知BindingList<T>通过弱引用附加到PropertyChanged事件(事实并非如此,但无论如何),而我的集合使用强引用。这可能导致内存泄漏,并且使用NotifyParentObservableCollection<T>的程序员必须调用Clear()来释放这些引用。

好吧,是时候停止懒惰并实现弱事件模式了。

我知道MSFT有WeakEventManager,但为了安全起见,我决定在网上搜索最佳的弱事件实现。引用Nathan Nesbit[^]

“网络上有很多实现某种弱监听器的例子——但有趣的是,几乎所有这些例子都有一些缺陷。最后,我们决定使用WeakEventManager及其相关的IWeakEventListener接口。”

所有其他解决方案都创建一个包装类的实例,该类使用强引用附加到源,并使用弱引用附加到目标。每次源事件触发时,包装器都会检查目标是否仍然存在。如果目标已死,则包装器会从源分离并可以被垃圾回收。

WeakEventWithWrapper.png

不幸的是,这可能会导致内存泄漏。如果源事件从不触发,则可能会泄漏数千个包装器:解决事件问题:弱事件处理程序[^],C# 中的弱事件[^]。

看起来 MSFT 的方法最有意义:为每个事件类型创建一个单一的静态事件包装器,并在后台线程上清理未使用的连接。

我将我的集合重命名为StrongNotifyParentObservableCollection<T>,并使用WeakEventManagerIWeakEventListener重新实现了NotifyParentObservableCollection<T>(以及演示应用程序)。我编写了几个实现WeakEventManager的类(ChildPropertyChangedEventManagerAnyPropertyChangedEventManager),并在NotifyParentObservableCollection<T>以及ViewModel中实现了IWeakEventListener

瞧,不再有强引用了,一切都变弱了! 我的NotifyParentObservableCollection<T>使用WeakEventManager将子项的INPC事件转发给感兴趣的侦听器,就像BindingList<T>应该做的那样。

目标已实现。弱即是强。中国哲学再次正确。

NotifyParentObservableCollection<T>

private void AttachHandlers(IEnumerable items)
{
    if (items != null)
    {
        foreach (var item in items.OfType<inotifypropertychanged>())
        {
            AnyPropertyChangedEventManager.AddListener(item, this);
        }
    }
}

private void DetachHandlers(IEnumerable items)
{
    if (items != null)
    {
        foreach (var item in items.OfType<inotifypropertychanged>())
        {
            AnyPropertyChangedEventManager.RemoveListener(item, this);
        }
    }
}

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
    if (managerType == typeof(AnyPropertyChangedEventManager))
    {
        if (SupportsChangeNotifications)
            OnChildPropertyChanged(this, 
                                   sender,
                                   ((PropertyChangedEventArgs)e).PropertyName);
    }
    return true;
}

void OnChildPropertyChanged(object sender, object source, string property)
{
    var childPropertyChanged = ChildPropertyChanged;
    if (childPropertyChanged != null)
    {
        childPropertyChanged(sender, 
                             new ChildPropertyChangedEventArgs(source, property));
    }
}

AnyPropertyChangedEventManager

public class AnyPropertyChangedEventManager : WeakEventManager
{
    private AnyPropertyChangedEventManager() { }

    public static void AddListener(INotifyPropertyChanged source, 
                                   IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(INotifyPropertyChanged source, 
                                      IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    protected override void StartListening(object source)
    {
        ((INotifyPropertyChanged)source).PropertyChanged += DeliverEvent;
    }

    protected override void StopListening(object source)
    {
        ((INotifyPropertyChanged)source).PropertyChanged -= DeliverEvent;
    }

    private static AnyPropertyChangedEventManager CurrentManager
    {
        get
        {
            var managerType = typeof(AnyPropertyChangedEventManager);
            var manager = (AnyPropertyChangedEventManager)
                                 GetCurrentManager(managerType);
            if (manager == null)
            {
                manager = new AnyPropertyChangedEventManager();
                SetCurrentManager(managerType, manager);
            }
            return manager;
        }
    }
}

ViewModel

private NotifyParentObservableCollection<LookupItem> _selectors;
public NotifyParentObservableCollection<LookupItem> Selectors
{
    get { return _selectors; }
    private set
    {
        if (_selectors != null)
        {
            ChildPropertyChangedEventManager.RemoveListener(_selectors, this);
            CollectionChangedEventManager.RemoveListener(_selectors, this);
        }
        _selectors = value;
        if (_selectors != null)
        {
            ChildPropertyChangedEventManager.AddListener(_selectors, this);
            CollectionChangedEventManager.AddListener(_selectors, this);
        }
        OnPropertyChanged("Selectors");
    }
}

public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e)
{
    if (managerType == typeof(CollectionChangedEventManager))
    {
        // Put all your CollectionChanged event handlers here
        if (sender == Selectors)
            OnSelectorsCollectionChanged(sender, (NotifyCollectionChangedEventArgs)e);
    }
    else if (managerType == typeof(ChildPropertyChangedEventManager))
    {
        // Put all your ChildPropertyChanged event handlers here
        if (sender == Selectors)
            OnSelectorsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
        else if (sender == Words)
            OnWordsChildPropertyChanged(sender, (ChildPropertyChangedEventArgs)e);
    }
    else if (managerType == typeof(AnyPropertyChangedEventManager))
    {
        // Put all your PropertyChanged event handlers here
    }
    return true;
}

ChildPropertyChangedEventManager

public class ChildPropertyChangedEventManager : WeakEventManager
{
    private ChildPropertyChangedEventManager() { }

    public static void AddListener(IChildPropertyChanged source, 
                                   IWeakEventListener listener)
    {
        CurrentManager.ProtectedAddListener(source, listener);
    }

    public static void RemoveListener(IChildPropertyChanged source, 
                                      IWeakEventListener listener)
    {
        CurrentManager.ProtectedRemoveListener(source, listener);
    }

    protected override void StartListening(object source)
    {
        ((IChildPropertyChanged)source).ChildPropertyChanged += DeliverEvent;
    }
 
    protected override void StopListening(object source)
    {
        ((IChildPropertyChanged)source).ChildPropertyChanged -= DeliverEvent;
    }

    private static ChildPropertyChangedEventManager CurrentManager
    {
        get
        {
            var managerType = typeof(ChildPropertyChangedEventManager);
            var manager = (ChildPropertyChangedEventManager)
                                        GetCurrentManager(managerType);
            if (manager == null)
            {
                manager = new ChildPropertyChangedEventManager();
                SetCurrentManager(managerType, manager);
            }
            return manager;
        }
    }
}

修订历史

  • 2011年1月23日 - 创建了这篇文章。
© . All rights reserved.