【发布时间】:2020-10-17 07:44:17
【问题描述】:
根据这篇文章:Observable Stack and Queue,我实现了一个 ObservableStack。它在 99% 的时间都在工作,但在某些情况下 - 很少而且似乎没有理由 - 当我尝试弹出堆栈时,我得到一个异常:
System.InvalidOperationException: Added item does not appear at given index '1'.
at MS.Internal.Data.EnumerableCollectionView.ProcessCollectionChanged(NotifyCollectionChangedEventArgs args)
at System.Windows.Data.CollectionView.OnCollectionChanged(Object sender, NotifyCollectionChangedEventArgs args)
at System.Collections.Specialized.NotifyCollectionChangedEventHandler.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
...
以下是相关代码:
public class ObservableStack<T> : Stack<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
public virtual event NotifyCollectionChangedEventHandler CollectionChanged;
public new virtual void Push(T item)
{
base.Push(item);
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, base.Count-1);
if (this.CollectionChanged != null)
this.CollectionChanged(this, e);
}
public new virtual T Pop()
{
var item = base.Pop();
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, base.Count);
if (this.CollectionChanged != null)
this.CollectionChanged(this, e);
return item;
}
//...
}
在极少数情况下,在 Pop() 中调用 CollectionChanged() 时会引发异常。这类似于问题 INotifyCollectionChanged: Added item does not appear at given index '0' ,但在这种情况下似乎没有一个答案适用。请注意,在引发异常的 Remove 行上将 base.count 替换为 -1 或 0 会导致它总是失败。同样,如果我只是从事件参数中排除索引,它总是会抱怨“集合删除事件必须指定项目位置”。从技术上讲,我可以通过将通知参数更改为
var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
...但当然,这实际上并不正确,我想让“删除”正确报告。插入的所有项目都没有被覆盖的 Equals() 或 operator=。
更多背景知识:堆栈在 UndoHistory 中使用,当值更改时,先前的值被推送到堆栈,并在我撤消时弹出。这适用于绝大多数情况,这就是为什么我很难弄清楚为什么错误仅在少数情况下发生。不幸的是,到目前为止,我一直未能成功地将其缩减为一个始终显示问题的小型独立示例。例如,在实际应用程序中,我可能会执行一个操作并撤消它没有问题,但随后连续执行两次相同的操作,之后撤消将引发异常。在一个较小/简化的示例中,我根本无法让它发生。请注意,堆栈还绑定到 ItemsControl 以显示撤消历史记录 - 尽管它只是单向的(即控件不用于修改堆栈,仅显示其项目)。
任何关于为什么它会在这种看似随机、罕见的情况下抛出这种异常的想法将不胜感激。
【问题讨论】:
-
是否涉及多个线程?请注意,您可以编写一条语句,而不是在调用 CollectionChanged 之前检查它是否为 null:
CollectionChanged?.Invoke(this, e); -
我不相信,但只是为了检查,我添加了 Console.WriteLine("Thread ID: " + System.Threading.Thread.CurrentThread.ManagedThreadId); - Push() 和 Pop(),& 他们总是报告相同的 ID 1。所以如果这是一种有效的检查方式,那么不,只涉及一个线程。
-
但是,堆栈有多个实例。不应该在不同的线程上。
-
您应该提供引发异常的代码行和重现错误的最小示例。
-
坦率地说,我对您实现这一点的方法持怀疑态度。使堆栈或队列“可观察”是不寻常的,因为这些数据结构通常旨在一次仅“观察”一个元素。就您可能提供面向集合的视图而言,
ObservableCollection<T>类本身直接提供了这一点。 IE。而不是自己实现INotifyCollectionChanged,您的堆栈和队列类应该使用ObservableCollection<T>作为支持数据结构,并将事件处理委托给它。
标签: c# wpf observablecollection