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

System.InvalidOperationException 'n' index in collection change event is not valid for collection of size '0'

在 INotifyCollectionChanged 的​​自定义实现上触发 CollectionChanged 事件时出现此异常:

An exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll but was not handled in user code

Additional information: '25' index in collection change event is not valid for collection of size '0'.

A XAML Datagrid 作为 ItemsSource 绑定到集合。

如何避免这种异常的发生?

代码如下:

public class MultiThreadObservableCollection<T> : ObservableCollection<T>
{
    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 中提供的索引是否属于集合;如果不是,则抛出主题中的异常。

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

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

这是一个有效的实现:

public class MultiThreadObservableCollection<T> : ObservableCollectionEnh<T>
{
    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