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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/224155/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 19:43:33  来源:igfitidea点击:

When Clearing an ObservableCollection, There are No Items in e.OldItems

wpfobservablecollection

提问by cplotts

I have something here that is really catching me off guard.

我这里有一些东西真的让我措手不及。

I have an ObservableCollection of T that is filled with items. I also have an event handler attached to the CollectionChanged event.

我有一个充满项目的 T 的 ObservableCollection。我还有一个附加到 CollectionChanged 事件的事件处理程序。

When you Clearthe collection it causes an CollectionChanged event with e.Action set to NotifyCollectionChangedAction.Reset. Ok, that's normal. But what is weird is that neither e.OldItems or e.NewItems has anything in it. I would expect e.OldItems to be filled with all items that were removed from the collection.

当您清除集合时,它会导致一个 CollectionChanged 事件,其中 e.Action 设置为 NotifyCollectionChangedAction.Reset。好吧,这很正常。但奇怪的是,e.OldItems 或 e.NewItems 中都没有任何内容。我希望 e.OldItems 填充从集合中删除的所有项目。

Has anyone else seen this? And if so, how have they gotten around it?

有没有其他人看到这个?如果是这样,他们是如何绕过它的?

Some background: I am using the CollectionChanged event to attach and detach from another event and thus if I don't get any items in e.OldItems ... I won't be able to detach from that event.

一些背景:我使用 CollectionChanged 事件来附加和分离另一个事件,因此如果我没有在 e.OldItems 中获得任何项目......我将无法从该事件中分离。



CLARIFICATION:I do know that the documentation doesn't outrightstate that it has to behave this way. But for every other action, it is notifying me of what it has done. So, my assumption is that it would tell me ... in the case of Clear/Reset as well.

澄清:我知道文档并没有直接说明它必须以这种方式行事。但是对于其他每一个动作,它都会通知我它做了什么。所以,我的假设是它会告诉我......在清除/重置的情况下也是如此。



Below is the sample code if you wish to reproduce it yourself. First off the xaml:

如果您想自己重现它,下面是示例代码。首先关闭xaml:

<Window
    x:Class="ObservableCollection.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1"
    Height="300"
    Width="300"
>
    <StackPanel>
        <Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
        <Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
        <Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
        <Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
        <Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
    </StackPanel>
</Window>

Next, the code behind:

接下来,后面的代码:

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
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    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<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}

采纳答案by cplotts

Ok, even though I still wish that ObservableCollection behaved as I wished ... the code below is what I ended up doing. Basically, I created a new collection of T called TrulyObservableCollection and overrided the ClearItems method which I then used to raise a Clearing event.

好吧,尽管我仍然希望 ObservableCollection 表现得如我所愿……下面的代码是我最终做的。基本上,我创建了一个名为 TrulyObservableCollection 的新 T 集合,并覆盖了 ClearItems 方法,然后我用它来引发 Clearing 事件。

In the code that uses this TrulyObservableCollection, I use this Clearing event to loop through the items that are still in the collection at that pointto do the detach on the event that I was wishing to detach from.

在使用此 TrulyObservableCollection 的代码中,我使用此 Clearing 事件循环遍历此时仍在集合中的项目以对我希望从中分离的事件执行分离。

Hope this approach helps someone else as well.

希望这种方法也能帮助其他人。

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

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

回答by Orion Edwards

It doesn't claim to include the old items, because Reset doesn't mean that the list has been cleared

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

It means that some dramatic thing has taken place, and the cost of working out the add/removes would most likely exceed the cost of just re-scanning the list from scratch... so that's what you should do.

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

MSDN suggests an example of the entire collection being re-sorted as a candidate for reset.

MSDN 提出了一个将整个集合重新排序为重置候选者的示例。

To reiterate. Reset doesn't mean clear, it means Your assumptions about the list are now invalid. Treat it as if it's an entirely new list. Clear happens to be one instance of this, but there could well be others.

重申。重置并不意味着 clear,这意味着您对列表的假设现在无效。把它当作一个全新的列表来对待。Clear 恰好是这样的一个实例,但很可能还有其他实例。

Some examples:
I've had a list like this with a lot of items in it, and it has been databound to a WPF ListViewto display on-screen.
If you clear the list and raise the .Resetevent, the performance is pretty much instant, but if you instead raise many individual .Removeevents, the performance is terrible, as WPF removes the items one by one. I've also used .Resetin my own code to indicate that the list has been re-sorted, rather than issuing thousands of individual Moveoperations. As with Clear, there is a large performance hit when when raising many individual events.

一些示例:
我有一个这样的列表,其中包含很多项目,并且它已被数据绑定到 WPFListView以显示在屏幕上。
如果您清除列表并引发.Reset事件,则性能几乎是即时的,但是如果您改为引发许多单个.Remove事件,则性能很糟糕,因为 WPF 会一个一个地删除项目。我还在.Reset我自己的代码中使用来指示列表已重新排序,而不是发出数千个单独的Move操作。与 Clear 一样,在引发许多单个事件时,性能会受到很大影响。

回答by decasteljau

We had the same issue here. The Reset action in CollectionChanged does not include the OldItems. We had a workaround: we used instead the following extension method:

我们在这里遇到了同样的问题。CollectionChanged 中的重置操作不包括 OldItems。我们有一个解决方法:我们改为使用以下扩展方法:

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

We ended up not supporting the Clear() function, and throwing a NotSupportedException in CollectionChanged event for Reset actions. The RemoveAll will trigger a Remove action in CollectionChanged event, with the proper OldItems.

我们最终不支持 Clear() 函数,并在 CollectionChanged 事件中为重置操作抛出 NotSupportedException。RemoveAll 将在 CollectionChanged 事件中触发 Remove 操作,并使用适当的 OldItems。

回答by grantnz

Another option is to replace the Reset event with a single Remove event that has all the cleared items in its OldItems property as follows:

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

public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
    protected override void ClearItems()
    {
        List<T> removed = new List<T>(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
    ...
}

Advantages:

好处:

  1. No need to subscribe to an additional event (as required by accepted answer)

  2. Doesn't generate an event for each object removed (some other proposed solutions result in multiple Removed events).

  3. Subscriber only needs to check NewItems & OldItems on any event to add/remove event handlers as required.

  1. 无需订阅其他事件(根据已接受的答案的要求)

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

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

Disadvantages:

缺点:

  1. No Reset event

  2. Small (?) overhead creating copy of list.

  3. ???

  1. 无重置事件

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

  3. ???

EDIT 2012-02-23

编辑 2012-02-23

Unfortunately, when bound to WPF list based controls, Clearing a ObservableCollectionNoReset collection with multiple elements will result in an exception "Range actions not supported". To be used with controls with this limitation, I changed the ObservableCollectionNoReset class to:

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

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

    protected override void ClearItems()
    {
        if (RangeActionsSupported)
        {
            List<T> removed = new List<T>(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.
 }

This isn't as efficient when RangeActionsSupported is false (the default) because one Remove notification is generated per object in the collection

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

回答by Alain

I've found a solution that allows the user to both capitalize on the efficiency of adding or removing many items at a time while only firing one event - and satisfy the needs of UIElements to get the Action.Reset event args while all other users would like a list of elements added and removed.

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

This solution involves overriding the CollectionChanged event. When we go to fire this event, we can actually look at the target of each registered handler and determine their type. Since only ICollectionView classes require NotifyCollectionChangedAction.Resetargs when more than one item changes, we can single them out, and give everyone else proper event args that contain the full list of items removed or added. Below is the implementation.

此解决方案涉及覆盖 CollectionChanged 事件。当我们去触发这个事件时,我们实际上可以查看每个注册处理程序的目标并确定它们的类型。由于只有 ICollectionView 类NotifyCollectionChangedAction.Reset在不止一项更改时需要args,因此我们可以将它们单独列出,并为其他所有人提供适当的事件 args,其中包含删除或添加的项目的完整列表。下面是实现。

public class BaseObservableCollection<T> : ObservableCollection<T>
{
    //Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) 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<T> 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<T> removed = new List<T>(this);
        _SuppressCollectionChanged = true;
        base.ClearItems();
        _SuppressCollectionChanged = false;
        OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
    }

    public void Add(IEnumerable<T> 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<T>(toAdd)));
    }

    public void Remove(IEnumerable<T> 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<T>(toRemove)));
    }
    #endregion
}

回答by DeadlyEmbrace

Okay, I know this is a very old question but I have come up with a good solution to the issue and thought I would share. This solution takes inspiration from a lot of the great answers here but has the following advantages:

好的,我知道这是一个非常古老的问题,但我想出了一个很好的解决方案,并认为我会分享。该解决方案从这里的许多优秀答案中汲取灵感,但具有以下优点:

  • No need to create a new class and override methods from ObservableCollection
  • Does not tamper with the workings of NotifyCollectionChanged (so no messing with Reset)
  • Does not make use of reflection
  • 无需创建新类并覆盖 ObservableCollection 中的方法
  • 不会篡改 NotifyCollectionChanged 的​​工作原理(因此不会干扰 Reset)
  • 不使用反射

Here is the code:

这是代码:

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

This extension method simply takes an Actionwhich will be invoked before the collection is cleared.

这个扩展方法只需要一个Action将在集合被清除之前调用的。

回答by cplotts

I tackled this one in a slightly different manner as I wanted to register to one event and handle all additions and removals in the event handler. I started off overriding the collection changed event and redirecting reset actions to removal actions with a list of items. This all went wrong as I was using the observable collection as an items source for a collection view and got "Range actions not supported".

我以稍微不同的方式解决了这个问题,因为我想注册一个事件并处理事件处理程序中的所有添加和删除。我开始覆盖集合更改事件并将重置操作重定向到带有项目列表的删除操作。这一切都出错了,因为我使用 observable 集合作为集合视图的项目源并得到“不支持范围操作”。

I finally created a new event called CollectionChangedRange which acts in the manner I expected the inbuilt version to act.

我最终创建了一个名为 CollectionChangedRange 的新事件,它的行为方式与我期望内置版本的行为方式相同。

I can't imagine why this limitation would be allowed and hope that this post at least stops others from going down the dead end that I did.

我无法想象为什么会允许这种限制,并希望这篇文章至少能阻止其他人走上我所做的死胡同。

/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
    private bool _addingRange;

    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
    {
        if ((CollectionChangedRange == null) || _addingRange) return;
        using (BlockReentrancy())
        {
            CollectionChangedRange(this, e);
        }
    }

    public void AddRange(IEnumerable<T> collection)
    {
        CheckReentrancy();
        var newItems = new List<T>();
        if ((collection == null) || (Items == null)) return;
        using (var enumerator = collection.GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                _addingRange = true;
                Add(enumerator.Current);
                _addingRange = false;
                newItems.Add(enumerator.Current);
            }
        }
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
    }

    protected override void ClearItems()
    {
        CheckReentrancy();
        var oldItems = new List<T>(this);
        base.ClearItems();
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
    }

    protected override void InsertItem(int index, T item)
    {
        CheckReentrancy();
        base.InsertItem(index, item);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
        CheckReentrancy();
        var item = base[oldIndex];
        base.MoveItem(oldIndex, newIndex);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
    }

    protected override void RemoveItem(int index)
    {
        CheckReentrancy();
        var item = base[index];
        base.RemoveItem(index);
        OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
    }

    protected override void SetItem(int index, T item)
    {
        CheckReentrancy();
        var oldItem = base[index];
        base.SetItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
    }
}

/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

    public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
    {
        list.CollectionChangedRange += HandleCollectionChangedRange;
    }

    private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
    {
        OnCollectionChangedRange(e);
    }

    protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
    {
        if (CollectionChangedRange != null)
        {
            CollectionChangedRange(this, args);
        }
    }

}

回答by Nir

This is how ObservableCollection works, you can work around this by keeping your own list outside of the ObservableCollection (adding to the list when action is Add, remove when action is Remove etc.) then you can get all the removed items (or added items) when action is Reset by comparing your list with the ObservableCollection.

这就是 ObservableCollection 的工作原理,您可以通过将自己的列表保留在 ObservableCollection 之外来解决此问题(在操作为 Add 时添加到列表,在操作为 Remove 时删除等),然后您可以获得所有已删除的项目(或添加的项目) ) 通过将您的列表与 ObservableCollection 进行比较来重置操作。

Another option is to create your own class that implements IList and INotifyCollectionChanged, then you can attach and detach events from within that class (or set OldItems on Clear if you like) - it's really not difficult, but it is a lot of typing.

另一种选择是创建您自己的实现 IList 和 INotifyCollectionChanged 的​​类,然后您可以从该类中附加和分离事件(如果您愿意,也可以将 OldItems 设置为 Clear)——这真的不难,但需要大量输入。

回答by Chris

For the scenario of attaching and detaching event handlers to the elements of the ObservableCollection there is also a "client-side" solution. In the event handling code you can check if the sender is in the ObservableCollection using the Contains method. Pro: you can work with any existing ObservableCollection. Cons: the Contains method runs with O(n) where n is the number of elements in the ObservableCollection. So this is a solution for small ObservableCollections.

对于将事件处理程序附加和分离到 ObservableCollection 的元素的场景,还有一个“客户端”解决方案。在事件处理代码中,您可以使用 Contains 方法检查发件人是否在 ObservableCollection 中。优点:您可以使用任何现有的 ObservableCollection。缺点:Contains 方法以 O(n) 运行,其中 n 是 ObservableCollection 中的元素数。所以这是小型 ObservableCollections 的解决方案。

Another "client-side" solution is to use an event handler in the middle. Just register all events to the event handler in the middle. This event handler in turn notifies the real event handler trough a callback or an event. If a Reset action occurs remove the callback or event create a new event handler in the middle and forget about the old one. This approach also works for big ObservableCollections. I used this for the PropertyChanged event (see code below).

另一个“客户端”解决方案是在中间使用事件处理程序。只需将所有事件注册到中间的事件处理程序即可。此事件处理程序依次通过回调或事件通知真实的事件处理程序。如果发生重置操作,请删除回调或事件,在中间创建一个新的事件处理程序并忘记旧的事件处理程序。这种方法也适用于大型 ObservableCollections。我将它用于 PropertyChanged 事件(请参阅下面的代码)。

    /// <summary>
    /// Helper class that allows to "detach" all current Eventhandlers by setting
    /// DelegateHandler to null.
    /// </summary>
    public class PropertyChangedDelegator
    {
        /// <summary>
        /// Callback to the real event handling code.
        /// </summary>
        public PropertyChangedEventHandler DelegateHandler;
        /// <summary>
        /// Eventhandler that is registered by the elements.
        /// </summary>
        /// <param name="sender">the element that has been changed.</param>
        /// <param name="e">the event arguments</param>
        public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
        {
            if (DelegateHandler != null)
            {
                DelegateHandler(sender, e);
            }
            else
            {
                INotifyPropertyChanged s = sender as INotifyPropertyChanged;
                if (s != null)
                    s.PropertyChanged -= PropertyChangedHandler;
            }   
        }
    }

回答by Rick Beerendonk

The ObservableCollection as well as the INotifyCollectionChanged interface are clearly written with a specific use in mind: UI building and its specific performance characteristics.

ObservableCollection 和 INotifyCollectionChanged 接口在编写时考虑了特定用途:UI 构建及其特定的性能特征。

When you want notifications of collection changes then you are generally only interested in Add and Remove events.

当您想要集合更改的通知时,您通常只对添加和删除事件感兴趣。

I use the following interface:

我使用以下界面:

using System;
using System.Collections.Generic;

/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
    /// <summary>
    /// Occurs when elements have been added.
    /// </summary>
    event EventHandler<NotifyCollectionEventArgs<T>> Added;

    /// <summary>
    /// Occurs when elements are about to be removed.
    /// </summary>
    event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}

/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
    /// <summary>
    /// Gets or sets the elements.
    /// </summary>
    /// <value>The elements.</value>
    public IEnumerable<T> Items
    {
        get;
        set;
    }
}

I've also written my own overload of Collection where:

我还编写了自己的 Collection 重载,其中:

  • ClearItems raises Removing
  • InsertItem raises Added
  • RemoveItem raises Removing
  • SetItem raises Removing and Added
  • ClearItems 引发移除
  • InsertItem 引发 添加
  • RemoveItem 引发移除
  • SetItem 引发删除和添加

Of course, AddRange can be added as well.

当然,也可以添加 AddRange。