集合更改事件中的System.InvalidOperationException’n’索引对于大小为’0’的集合无效

我在INotifyCollectionChanged的自定义实现上触发CollectionChanged事件时遇到此exception:

PresentationFramework.dll中出现“System.InvalidOperationException”类型的exception,但未在用户代码中处理

附加信息:集合更改事件中的’25’索引对于大小为’0’的集合无效。

XAML Datagrid作为ItemsSource绑定到集合。

如何避免这种exception发生?

代码如下:

public class MultiThreadObservableCollection : ObservableCollection { private readonly object lockObject; public MultiThreadObservableCollection() { lockObject = new object(); } private NotifyCollectionChangedEventHandler myPropertyChangedDelegate; public override event NotifyCollectionChangedEventHandler CollectionChanged { add { lock (this.lockObject) { myPropertyChangedDelegate += value; } } remove { lock (this.lockObject) { myPropertyChangedDelegate -= value; } } } protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var eh = this.myPropertyChangedDelegate; if (eh != null) { Dispatcher dispatcher; lock (this.lockObject) { dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() let dpo = nh.Target as DispatcherObject where dpo != null select dpo.Dispatcher).FirstOrDefault(); } if (dispatcher != null && dispatcher.CheckAccess() == false) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => this.OnCollectionChanged(e))); } else { lock (this.lockObject) { foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) { nh.Invoke(this, e); } } } } } 

错误发生在以下行:

 nh.Invoke(this, e); 

谢谢!

关键是(按设计)nh.Invoke(this,e); 被异步调用。 当集合绑定在XAML中并且集合发生更改时,将调用System.Windows.Data.ListCollectionView的私有方法AdjustBefore。 这里,ListCollectionView检查eventArgs中提供的索引是否属于集合; 如果不是,则抛出主题中的exception。

在问题中报告的实现中,NotifyCollectionChangedEventHandler在延迟时间被调用,此时集合可能已经被更改,并且eventArgs中提供的索引可能不再属于它。

避免ListCollectionView执行此检查的一种方法是使用新的eventargs替换eventargs,而不是报告添加或删除的项目,只是有一个Reset操作(当然,效率会丢失!)。

这是一个有效的实现:

 public class MultiThreadObservableCollection : ObservableCollectionEnh { public override event NotifyCollectionChangedEventHandler CollectionChanged; protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { var eh = CollectionChanged; if (eh != null) { Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList() let dpo = nh.Target as DispatcherObject where dpo != null select dpo.Dispatcher).FirstOrDefault(); if (dispatcher != null && dispatcher.CheckAccess() == false) { dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => this.OnCollectionChanged(e))); } else { // IMPORTANT NOTE: // We send a Reset eventargs (this is inefficient). // If we send the event with the original eventargs, it could contain indexes that do not belong to the collection any more, // causing an InvalidOperationException in the with message like: // 'n2' index in collection change event is not valid for collection of size 'n2'. NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset); foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()) { nh.Invoke(this, notifyCollectionChangedEventArgs); } } } } } 

参考文献: https : //msdn.microsoft.com/library/system.windows.data.listcollectionview(v=vs.110).aspx

https://msdn.microsoft.com/library/ms752284(v=vs.110).aspx