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






4.85/5 (10投票s)
ObservableCollection使用弱事件模式附加到其子项的PropertyChanged事件,并相应地引发ChildPropertyChanged事件。复制BindingList的功能,但没有开销。当您需要更新/选择/排列/移动项目时非常有用。
目录
- NotifyParentObservableCollection (Part 1) - 使用强事件实现
- NotifyParentObservableCollection (Part 2) - 使用弱事件实现
弱事件
使用强事件处理程序实现,NotifyParentObservableCollection<T>
我的集合本来应该取代BindingList<T>
提供的功能(转发其子项的INotifyPropertyChanged
事件)。
我被告知BindingList<T>
通过弱引用附加到PropertyChanged
事件(事实并非如此,但无论如何),而我的集合使用强引用。这可能导致内存泄漏,并且使用NotifyParentObservableCollection<T>
的程序员必须调用Clear()
来释放这些引用。
好吧,是时候停止懒惰并实现弱事件模式了。
我知道MSFT有WeakEventManager
,但为了安全起见,我决定在网上搜索最佳的弱事件实现。引用Nathan Nesbit[^]
“网络上有很多实现某种弱监听器的例子——但有趣的是,几乎所有这些例子都有一些缺陷。最后,我们决定使用WeakEventManager
及其相关的IWeakEventListener
接口。”
所有其他解决方案都创建一个包装类的实例,该类使用强引用附加到源,并使用弱引用附加到目标。每次源事件触发时,包装器都会检查目标是否仍然存在。如果目标已死,则包装器会从源分离并可以被垃圾回收。
不幸的是,这可能会导致内存泄漏。如果源事件从不触发,则可能会泄漏数千个包装器:解决事件问题:弱事件处理程序[^],C# 中的弱事件[^]。
看起来 MSFT 的方法最有意义:为每个事件类型创建一个单一的静态事件包装器,并在后台线程上清理未使用的连接。
我将我的集合重命名为StrongNotifyParentObservableCollection<T>
,并使用WeakEventManager
和IWeakEventListener
重新实现了NotifyParentObservableCollection<T>
(以及演示应用程序)。我编写了几个实现WeakEventManager
的类(ChildPropertyChangedEventManager
和AnyPropertyChangedEventManager
),并在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日 - 创建了这篇文章。