ObservableCollection:使用多个新项调用OnCollectionChanged

请注意,我正在尝试使用NotifyCollectionChangedAction.Add操作而不是.Reset。 后者确实有效,但对于大型集合来说效率不高。

所以我inheritance了ObservableCollection:

public class SuspendableObservableCollection : ObservableCollection 

出于某种原因,这段代码:

 private List _cachedItems; ... public void FlushCache() { if (_cachedItems.Count > 0) { foreach (var item in _cachedItems) Items.Add(item); OnCollectionChanged(new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Add, (IList)_cachedItems)); } } 

抛出一个集合添加事件是指不属于集合的项目

这似乎是BCL的一个错误?

我可以在调用OnCollectionChanged之前逐步查看将新项添加到this.Items

刚刚发现了一个惊人的发现。 这些方法都不适合我(flush,addrange),因为只有当这个集合绑定到我的Listview时才会触发错误!

 TestObservableCollection testCollection = new TestObservableCollection(); List testTrades = new List(); for (int i = 0; i < 200000; i++) testTrades.Add(t); testCollection.AddRange(testTrades); // no problems here.. _trades.AddRange(testTrades); // this one is bound to ListView .. BOOOM!!! 

总之,ObservableCollection确实支持添加增量列表,但ListView不支持。 Andyp想出了一个解决方法,使其适用于下面的CollectionView,但是由于调用了.Refresh(),这与调用OnCollectionChanged(.Reset)没什么不同。

您可以为ObservableCollection实现AddRange(),如下所示:

 public class RangeObservableCollection : ObservableCollection { private bool _SuppressNotification; public override event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChangedMultiItem( NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handlers = this.CollectionChanged; if (handlers != null) { foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { if (handler.Target is CollectionView) ((CollectionView)handler.Target).Refresh(); else handler(this, e); } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (!_SuppressNotification) { base.OnCollectionChanged(e); if (CollectionChanged != null) CollectionChanged.Invoke(this, e); } } public void AddRange(IEnumerable list) { if (list == null) throw new ArgumentNullException("list"); _SuppressNotification = true; foreach (T item in list) { Add(item); } _SuppressNotification = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list)); } } 

更新:绑定到ListBox后,我也看到了InvalidOperationException(您看到的消息相同)。 根据这篇文章 ,因为CollectionView不支持范围操作。 幸运的是,这篇文章也提供了一个解决方案(虽然它感觉有点“hack-ish”)。

更新2:添加了一个修复程序,在重写的OnCollectionChanged()实现中引发重写的CollectionChanged事件。

感谢AndyP的灵感。 我的实现有一些问题,例如在测试中使用CollectionView而不是ICollectionView,以及在元素上手动调用“Reset”。 从CollectionViewinheritance的元素可能实际上以比调用“this.Reset()”更多的方式处理这些args,所以最好仍然触发它们的处理程序,只需要使用它们需要的Action = Reset args而不是改进的事件args包括已更改的项目列表。 下面是我(非常相似)的实现。

 public class BaseObservableCollection : ObservableCollection { //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable) and Clear() private bool _SuppressCollectionChanged = false; /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args. public override event NotifyCollectionChangedEventHandler CollectionChanged; public BaseObservableCollection() : base(){} public BaseObservableCollection(IEnumerable data) : base(data){} #region Event Handlers protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if( !_SuppressCollectionChanged ) { base.OnCollectionChanged(e); if( CollectionChanged != null ) CollectionChanged.Invoke(this, e); } } //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable //for applications in code, so we actually check the type we're notifying on and pass a customized event args. protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e) { NotifyCollectionChangedEventHandler handlers = this.CollectionChanged; if( handlers != null ) foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() ) handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } #endregion #region Extended Collection Methods protected override void ClearItems() { if( this.Count == 0 ) return; List removed = new List(this); _SuppressCollectionChanged = true; base.ClearItems(); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed)); } public void Add(IEnumerable toAdd) { if( this == toAdd ) throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified."); _SuppressCollectionChanged = true; foreach( T item in toAdd ) Add(item); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List(toAdd))); } public void Remove(IEnumerable toRemove) { if( this == toRemove ) throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified."); _SuppressCollectionChanged = true; foreach( T item in toRemove ) Remove(item); _SuppressCollectionChanged = false; OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List(toRemove))); } #endregion } 

在多次迭代之后,我们最终得到了这个版本的ObservableRangeCollectionReadOnlyObservableRangeCollection ,它基于接受答案的代码,我们在过去的6个月内没有必要修改:

 public class ObservableRangeCollection : ObservableCollection { private bool suppressNotification; public ObservableRangeCollection() { } public ObservableRangeCollection(IEnumerable items) : base(items) { } public override event NotifyCollectionChangedEventHandler CollectionChanged; protected virtual void OnCollectionChangedMultiItem( NotifyCollectionChangedEventArgs e) { var handlers = CollectionChanged; if (handlers == null) return; foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { if (handler.Target is ReadOnlyObservableCollection && !(handler.Target is ReadOnlyObservableRangeCollection)) { throw new NotSupportedException( "ObservableRangeCollection is wrapped in ReadOnlyObservableCollection which might be bound to ItemsControl " + "which is internally using ListCollectionView which does not support range actions.\n" + "Instead of ReadOnlyObservableCollection, use ReadOnlyObservableRangeCollection"); } var collectionView = handler.Target as ICollectionView; if (collectionView != null) { collectionView.Refresh(); } else { handler(this, e); } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (suppressNotification) return; base.OnCollectionChanged(e); if (CollectionChanged != null) { CollectionChanged.Invoke(this, e); } } public void AddRange(IEnumerable items) { if (items == null) return; suppressNotification = true; var itemList = items.ToList(); foreach (var item in itemList) { Add(item); } suppressNotification = false; if (itemList.Any()) { OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, itemList)); } } public void AddRange(params T[] items) { AddRange((IEnumerable)items); } public void ReplaceWithRange(IEnumerable items) { Items.Clear(); OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); AddRange(items); } public void RemoveRange(IEnumerable items) { suppressNotification = true; var removableItems = items.Where(x => Items.Contains(x)).ToList(); foreach (var item in removableItems) { Remove(item); } suppressNotification = false; if (removableItems.Any()) { OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removableItems)); } } } public class ReadOnlyObservableRangeCollection : ReadOnlyObservableCollection { public ReadOnlyObservableRangeCollection(ObservableCollection list) : base(list) { } protected override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var handlers = CollectionChanged; if (handlers == null) return; foreach (NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList()) { var collectionView = handler.Target as ICollectionView; if (collectionView != null) { collectionView.Refresh(); } else { handler(this, e); } } } } 

我们基本上用ObservableCollection替换了我们应用程序中ObservableCollection所有用法,它就像一个魅力。

我相信你需要把它投射到IList

base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, (IList)_cachedItems));