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
在 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