1

INotifyCollectionChanged のカスタム実装で CollectionChanged イベントをトリガーすると、この例外が発生します。

タイプ 'System.InvalidOperationException' の例外が PresentationFramework.dll で発生しましたが、ユーザー コードで処理されませんでした

追加情報: コレクション変更イベントのインデックス '25' は、サイズ '0' のコレクションには無効です。

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

ありがとう!

4

1 に答える 1

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

于 2016-06-10T07:02:31.587 に答える