当清除一个ObservableCollection时,e.OldItems中没有任何项。

9 浏览
0 Comments

当清除一个ObservableCollection时,e.OldItems中没有任何项。

我这里有一件事情真的让我措手不及。

我有一个ObservableCollection的T,其中装满了项目。我还附加了一个事件处理程序到CollectionChanged事件上。

当你清空集合时,会触发一个e.Action被设置为NotifyCollectionChangedAction.Reset的CollectionChanged事件。好吧,这是正常的。但奇怪的是,e.OldItems和e.NewItems里面都没有任何内容。我希望e.OldItems里面填满了从集合中删除的所有项目。

还有其他人见过这种情况吗?如果有的话,他们是怎么解决的?

一些背景信息:我正在使用CollectionChanged事件来附加和解除与另一个事件的关联,因此如果我在e.OldItems中没有得到任何项目...我将无法解除与该事件的关联。

澄清:

我确实知道文档没有明确说明它必须以这种方式运行。但对于其他动作,它都在通知我它所做的事情。所以,我假设它会告诉我...在清空/重置的情况下也是如此。

如果您想要重现它,请参考以下示例代码。首先是xaml代码:


    
        

接下来是后台代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ObservableCollection
{
    /// 
    /// Interaction logic for Window1.xaml
    /// 
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
        }
        private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }
        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Add(25);
        }
        private void moveButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Move(0, 19);
        }
        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.RemoveAt(0);
        }
        private void replaceButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection[0] = 50;
        }
        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Clear();
        }
        private ObservableCollection _integerObservableCollection = new ObservableCollection { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}

0
0 Comments

这个问题的出现的原因是,在清空一个ObservableCollection时,e.OldItems中没有任何项。解决方法是创建一个扩展方法,该方法接受一个Action作为参数,在清空集合之前调用该Action。这个方法的代码如下:

public static void Clear(this ObservableCollection collection, Action> unhookAction)
{
    unhookAction.Invoke(collection);
    collection.Clear();
}

这个扩展方法的优点是:

- 不需要创建一个新的类并重写ObservableCollection的方法

- 不会影响NotifyCollectionChanged的工作方式(所以不会破坏Reset)

- 不使用反射

这个扩展方法非常简单、优雅,是一个很好的解决方案。很多人都喜欢这个方法。

0
0 Comments

当清空一个ObservableCollection时,e.OldItems中没有任何项的原因是因为Reset并不意味着列表已被清空,它意味着发生了一些戏剧性的事情,计算出添加/删除项的代价很可能超过了重新扫描整个列表的代价...所以你应该重新扫描列表。

MSDN建议将整个集合重新排序作为reset的候选项。

再次强调,Reset并不意味着清除,它意味着你对列表的假设现在无效了。将其视为一个全新的列表。清除只是其中的一种情况,但也可能还有其他情况。

一些例子:

我有一个包含许多项的列表,它已经通过数据绑定到WPF的ListView上进行显示。

如果清空列表并触发.Reset事件,性能几乎是瞬间的,但如果触发许多单独的.Remove事件,性能非常糟糕,因为WPF会逐个删除项。

我在自己的代码中也使用.Reset来表示列表已被重新排序,而不是发出成千上万个单独的.Move操作。与Clear一样,当触发许多单独的事件时,会有很大的性能损失。

我要对此提出异议。如果你查看文档,它声明:表示一个动态数据集合,当项目添加、删除或整个列表刷新时提供通知。

我只是想再次指出(就像我在问题中所做的那样),我知道文档并没有明确表示调用Clear应该通知你哪些项目被删除...但在这种情况下,我觉得这个集合并没有真正观察到变化。

文档说明它应该在项目添加/删除/刷新时通知你,但并不承诺告诉你所有项目的细节...只是告诉你事件发生了。从这个角度来看,这个行为是可以接受的。个人认为,他们应该把所有的项都放入OldItems中,当清除时(只是复制一个列表),但也许有一些场景这样做会太昂贵。不管怎样,如果你想要一个集合,它通知你所有删除的项,这并不难实现。

嗯,如果Reset表示一个昂贵的操作,那么将整个列表复制到OldItems中可能也会遵循同样的逻辑。

第一个评论中的链接已经失效了。我相信这个页面现在保存着相同的信息。

有趣的事实是:从.NET 4.5开始,Reset实际上意味着“集合的内容已被清除”。参见msdn.microsoft.com/en-us/library/...

这个答案没有太大帮助,抱歉。是的,如果你得到一个Reset,你可以重新扫描整个列表,但是你无法访问删除的项,而你可能需要从中删除事件处理程序。这是一个大问题。

更不用说Clear(而不是reset)也有相同的行为...

这个答案似乎只是为Microsoft不完整的ObservableCollection实现道歉。

这不是道歉,只是解释。Microsoft编写了所有的代码,并制定了这是什么或不是什么的规则。它不可能是“不完整的”,因为它们定义了“完整”的含义。你是否同意并不重要。

0
0 Comments

当清除一个ObservableCollection时,e.OldItems中没有任何项目是因为Reset动作在CollectionChanged事件中不包括OldItems。解决方法是使用以下扩展方法:

public static void RemoveAll(this IList list)
{
   while (list.Count > 0)
   {
      list.RemoveAt(list.Count - 1);
   }
}

我们最终选择不支持Clear()方法,并在CollectionChanged事件中的Reset动作中抛出NotSupportedException异常。使用RemoveAll方法将触发CollectionChanged事件中的Remove动作,并提供正确的OldItems。

这是一个不错的想法。我不喜欢不支持Clear,因为根据我的经验,大多数人都使用这个方法...但至少你通过异常警告了用户。

我同意,这不是理想的解决方法,但我们发现这是最好的可接受的解决方法。

你不应该使用旧的项目!你应该做的是将列表上的任何数据都清除,并重新扫描它,就像它是一个新列表一样!

问题是,Orion,你的建议适用于什么情况?当我有一些我想要从列表中分离事件的项目时,会发生什么?我不能简单地将数据倾倒在列表上...这会导致内存泄漏/压力。

现在,我确实理解你对Reset语义的争论,并且你提出了一些有趣的观点。但是,我很想看到一些关于你的建议的文档。

在这里还有另一个要点...当你收到Action为Reset的CollectionChanged事件时(通过调用Clear)...集合已经为空。这就是为什么你不能使用它来分离事件。所以,我不能简单地倾倒我的数据。它已经被倾倒了。

这种解决方法的主要缺点是,如果你删除了1000个项目,你会触发1000次CollectionChanged事件,UI需要更新Collectionview 1000次(更新UI元素是昂贵的)。如果你不害怕重写ObservableCollection类,你可以使它触发Clear()事件,但提供正确的事件参数,以允许监视代码注销所有已删除的元素。

0