当前位置:  开发笔记 > Android > 正文

清除ObservableCollection时,e.OldItems中没有项目

如何解决《清除ObservableCollection时,e.OldItems中没有项目》经验,为你挑选了6个好方法。

我这里有些东西让我措手不及.

我有一个带有项目的Ob的ObservableCollection.我还有一个附加到CollectionChanged事件的事件处理程序.

当您清除集合,将导致以e.Action的CollectionChanged事件设置为NotifyCollectionChangedAction.Reset.好的,这很正常.但奇怪的是,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 };
    }
}

Orion Edward.. 46

它不声称包含旧项目,因为重置并不意味着列表已被清除

这意味着发生了一些戏剧性的事情,并且计算添加/删除的成本很可能超过仅从头开始重新扫描列表的成本......所以这就是你应该做的.

MSDN建议将整个集合重新排序为重置候选者的示例.

重申一下.重置并不意味着明确,这意味着您对该列表的假设现在无效.把它看作是一个全新的清单.Clear恰好是其中的一个例子,但其他人可能也是如此.

一些例子:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到WPF ListView以显示在屏幕上.
如果你清除列表并提升.Reset事件,那么性能几乎是即时的,但是如果你提出很多单独的.Remove事件,性能就会很糟糕,因为WPF会逐个删除这些项目.我还在.Reset我自己的代码中使用来表明列表已经重新排序,而不是发出数千个单独的Move操作.与Clear一样,在筹集许多个人活动时会有很大的性能影响.



1> Orion Edward..:

它不声称包含旧项目,因为重置并不意味着列表已被清除

这意味着发生了一些戏剧性的事情,并且计算添加/删除的成本很可能超过仅从头开始重新扫描列表的成本......所以这就是你应该做的.

MSDN建议将整个集合重新排序为重置候选者的示例.

重申一下.重置并不意味着明确,这意味着您对该列表的假设现在无效.把它看作是一个全新的清单.Clear恰好是其中的一个例子,但其他人可能也是如此.

一些例子:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到WPF ListView以显示在屏幕上.
如果你清除列表并提升.Reset事件,那么性能几乎是即时的,但是如果你提出很多单独的.Remove事件,性能就会很糟糕,因为WPF会逐个删除这些项目.我还在.Reset我自己的代码中使用来表明列表已经重新排序,而不是发出数千个单独的Move操作.与Clear一样,在筹集许多个人活动时会有很大的性能影响.


对不起,这个答案没有多大帮助.是的,如果您获得重置,则可以重新扫描整个列表,但您无权删除项目,您可能需要从中删除事件处理程序.这是个大问题.
文档声明它应该在添加/删除/刷新项目时通知您,但它不会保证告诉您项目的所有详细信息......只是事件发生了.从这个角度来看,行为很好.我个人认为他们应该在清理时将所有项目放入"OldItems",(它只是复制一个列表),但也许有些情况下这太贵了.无论如何,如果你想要一个*会*通知你所有被删除项目的集合,那就不难了.
有趣的事实:因为**.NET 4.5**,`重置'实际上意味着"收集的内容被**清除**".请参阅https://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction%28v=vs.110%29.aspx
好吧,如果`Reset`表示昂贵的操作,很可能同样的推理适用于将整个列表复制到`OldItems`.

2> decasteljau..:

我们在这里遇到了同样的问题.CollectionChanged中的Reset操作不包含OldItems.我们有一个解决方法:我们使用以下扩展方法:

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

我们最终不支持Clear()函数,并在CollectionChanged事件中为Reset操作抛出NotSupportedException.RemoveAll将使用正确的OldItems在CollectionChanged事件中触发Remove操作.


问题,猎户座,你的建议......是提示这个问题的用例.当我在列表中有要从中分离事件的项目时会发生什么?我不能只将数据转储到列表中......这会导致内存泄漏/压力.
此解决方案的主要缺点是,如果删除1000个项目,则会激活CollectionChanged 1000次,并且UI必须更新CollectionView 1000次(更新UI元素非常昂贵).如果你不害怕覆盖ObservableCollection类,你可以使它触发Clear()事件但提供正确的事件Args允许监视代码取消注册所有被删除的元素.

3> grantnz..:

另一个选项是使用单个Remove事件替换Reset事件,该事件在其OldItems属性中包含所有已清除的项,如下所示:

public class ObservableCollectionNoReset : ObservableCollection
{
    protected override void ClearItems()
    {
        List removed = new List(this);
        base.ClearItems();
        base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }
    // Constructors omitted
    ...
}

好处:

    无需订阅其他活动(按照接受的答案要求)

    不为每个删除的对象生成事件(其他一些建议的解决方案会导致多个已删除的事件).

    订阅者只需要在任何事件上检查NewItems和OldItems,以根据需要添加/删除事件处理程序.

缺点:

    没有重置事件

    小(?)开销创建列表副本.

    ???

编辑2012-02-23

不幸的是,当绑定到基于WPF列表的控件时,使用多个元素清除ObservableCollectionNoReset集合将导致异常"不支持范围操作".要与具有此限制的控件一起使用,我将ObservableCollectionNoReset类更改为:

public class ObservableCollectionNoReset : ObservableCollection
{
    // Some CollectionChanged listeners don't support range actions.
    public Boolean RangeActionsSupported { get; set; }

    protected override void ClearItems()
    {
        if (RangeActionsSupported)
        {
            List removed = new List(this);
            base.ClearItems();
            base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
        }
        else
        {
            while (Count > 0 )
                base.RemoveAt(Count - 1);
        }                
    }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (e.Action != NotifyCollectionChangedAction.Reset)
            base.OnCollectionChanged(e);
    }

    public ObservableCollectionNoReset(Boolean rangeActionsSupported = false) 
    {
        RangeActionsSupported = rangeActionsSupported;
    }

    // Additional constructors omitted.
 }

当RangeActionsSupported为false(默认值)时,这不是那么有效,因为集合中的每个对象都会生成一个Remove通知


我喜欢这个解决方案,但它不起作用......除非操作是"重置",否则不允许提出一个更改了多个项目的NotifyCollectionChangedEventArgs.你得到一个例外`不支持范围操作.我不知道为什么会这样做,但是现在除了一次删除每个项目之外别无选择......
@Alain ObservableCollection没有强加这个限制.我怀疑你绑定了这个集合的WPF控件.我有同样的问题,从来没有用我的解决方案发布更新.我将使用修改后的类编辑我的答案,该类在绑定到WPF控件时起作用.

4> Alain..:

我找到了一个解决方案,允许用户同时利用添加或删除多个项目的效率,同时只触发一个事件 - 并满足UIElements的需求以获取Action.Reset事件args而所有其他用户将就像添加和删除的元素列表一样.

此解决方案涉及覆盖CollectionChanged事件.当我们开始触发此事件时,我们实际上可以查看每个已注册处理程序的目标并确定其类型.由于只有ICollectionView类NotifyCollectionChangedAction.Reset在多个项目发生更改时才需要args,因此我们可以将它们单独输出,并为其他人提供包含已删除或添加的完整项目列表的正确事件参数.以下是实施.

public class BaseObservableCollection : ObservableCollection
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable) and Clear()
    private bool _SuppressCollectionChanged = false;

    /// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
    public override event NotifyCollectionChangedEventHandler CollectionChanged;

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable data) : base(data){}

    #region Event Handlers
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if( !_SuppressCollectionChanged )
        {
            base.OnCollectionChanged(e);
            if( CollectionChanged != null )
                CollectionChanged.Invoke(this, e);
        }
    }

    //CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
    //one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
    //for applications in code, so we actually check the type we're notifying on and pass a customized event args.
    protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
        if( handlers != null )
            foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
                handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    #endregion

    #region Extended Collection Methods
    protected override void ClearItems()
    {
        if( this.Count == 0 ) return;

        List removed = new List(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable toAdd)
    {
        if( this == toAdd )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toAdd )
            Add(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List(toAdd)));
    }

    public void Remove(IEnumerable toRemove)
    {
        if( this == toRemove )
            throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");

        _SuppressCollectionChanged = true;
        foreach( T item in toRemove )
            Remove(item);
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List(toRemove)));
    }
    #endregion
}



5> 小智..:

好的,我知道这是一个非常古老的问题,但我已经想出了一个很好的解决方案,并且认为我会分享.这个解决方案从这里的很多很好的答案中获得灵感,但具有以下优点:

无需从ObservableCollection创建新类和覆盖方法

不会篡改NotifyCollectionChanged的工作方式(所以不要乱搞重置)

不利用反射

这是代码:

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

这个扩展方法只需要Action在清除集合之前调用它.



6> cplotts..:

好吧,即使我仍然希望ObservableCollection按照我的意愿行事......下面的代码就是我最终要做的.基本上,我创建了一个名为TrulyObservableCollection的新T集合,并覆盖了ClearItems方法,然后我用它来引发Clearing事件.

在使用此TrulyObservableCollection的代码中,我使用此清除事件来遍历当时仍在集合中的项目,以便对我希望从中分离的事件进行分离.

希望这种方法能够帮助其他人.

public class TrulyObservableCollection : ObservableCollection
{
    public event EventHandler Clearing;
    protected virtual void OnClearing(EventArgs e)
    {
        if (Clearing != null)
            Clearing(this, e);
    }

    protected override void ClearItems()
    {
        OnClearing(EventArgs.Empty);
        base.ClearItems();
    }
}


大声笑的名字.我同意这是对设计的严重疏忽.
推荐阅读
女女的家_747
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有