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

时间:2016-06-08 09:42:39

标签: c# wpf inotifycollectionchanged

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

  

类型' System.InvalidOperationException'的例外情况发生在   PresentationFramework.dll但未在用户代码中处理

     

其他信息:' 25'集合更改事件中的索引不是   适用于收集大小' 0'。

XAML Datagrid作为ItemsSource绑定到集合。

如何避免此异常发生?

代码如下:

Xamarin.forms

错误发生在以下行:

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);
                            }
                    }
                }
            }           
    }

谢谢!

1 个答案:

答案 0 :(得分:0)

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

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

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

这是一个有效的实施方案:

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