【问题标题】:ObservableCollection Doesn't support AddRange method, so I get notified for each item added, besides what about INotifyCollectionChanging?ObservableCollection 不支持 AddRange 方法,所以我会收到添加的每个项目的通知,除了 INotifyCollectionChanging 呢?
【发布时间】:2010-10-14 19:25:51
【问题描述】:

我希望能够添加一个范围并为整个批量更新。

我还希望能够在操作完成之前取消操作(即除了“已更改”之外的集合更改)。


相关问题 Which .Net collection for adding multiple objects at once and getting notified?

【问题讨论】:

    标签: c# vb.net observablecollection addrange inotifycollectionchanged


    【解决方案1】:

    请参考updated and optimized C# 7 version。我不想删除 VB.NET 版本,所以我只是将其发布在单独的答案中。

    Go to updated version

    好像不支持,我自己实现的,仅供参考,希望对你有帮助:

    我更新了 VB 版本,从现在开始,它会在更改集合之前引发一个事件,这样你就可以后悔(在与 DataGridListView 等一起使用时很有用,你可以显示“你确定吗”向用户确认),更新的 VB 版本在此消息的底部

    请接受我的歉意,屏幕太窄而无法包含我的代码,我也不喜欢它。

    VB.NET:

    Imports System.Collections.Specialized
    
    Namespace System.Collections.ObjectModel
        ''' <summary>
        ''' Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
        ''' </summary>
        ''' <typeparam name="T"></typeparam>
        Public Class ObservableRangeCollection(Of T) : Inherits System.Collections.ObjectModel.ObservableCollection(Of T)
    
            ''' <summary>
            ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
            ''' </summary>
            Public Sub AddRange(ByVal collection As IEnumerable(Of T))
                For Each i In collection
                    Items.Add(i)
                Next
                OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
            End Sub
    
            ''' <summary>
            ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
            ''' </summary>
            Public Sub RemoveRange(ByVal collection As IEnumerable(Of T))
                For Each i In collection
                    Items.Remove(i)
                Next
    
                OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
            End Sub
    
            ''' <summary>
            ''' Clears the current collection and replaces it with the specified item.
            ''' </summary>
            Public Sub Replace(ByVal item As T)
                ReplaceRange(New T() {item})
            End Sub
            ''' <summary>
            ''' Clears the current collection and replaces it with the specified collection.
            ''' </summary>
            Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T))
                Dim old = Items.ToList
                Items.Clear()
                For Each i In collection
                    Items.Add(i)
                Next
                OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
            End Sub
    
            ''' <summary>
            ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
            ''' </summary>
            ''' <remarks></remarks>
            Public Sub New()
                MyBase.New()
            End Sub
            ''' <summary>
            ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
            ''' </summary>
            ''' <param name="collection">collection: The collection from which the elements are copied.</param>
            ''' <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
            Public Sub New(ByVal collection As IEnumerable(Of T))
                MyBase.New(collection)
            End Sub
        End Class   
    
    End Namespace
    

    C#:

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Linq;
    
    /// <summary> 
    /// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
    /// </summary> 
    /// <typeparam name="T"></typeparam> 
    public class ObservableRangeCollection<T> : ObservableCollection<T>
    {
        /// <summary> 
        /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
        /// </summary> 
        public void AddRange(IEnumerable<T> collection)
        {
            if (collection == null) throw new ArgumentNullException("collection");
    
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        /// <summary> 
        /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
        /// </summary> 
        public void RemoveRange(IEnumerable<T> collection)
        {
            if (collection == null) throw new ArgumentNullException("collection");
    
            foreach (var i in collection) Items.Remove(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        /// <summary> 
        /// Clears the current collection and replaces it with the specified item. 
        /// </summary> 
        public void Replace(T item)
        {
            ReplaceRange(new T[] { item });
        }
    
        /// <summary> 
        /// Clears the current collection and replaces it with the specified collection. 
        /// </summary> 
        public void ReplaceRange(IEnumerable<T> collection)
        {
            if (collection == null) throw new ArgumentNullException("collection");
    
            Items.Clear();
            foreach (var i in collection) Items.Add(i);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
        /// </summary> 
        public ObservableRangeCollection()
            : base() { }
    
        /// <summary> 
        /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
        /// </summary> 
        /// <param name="collection">collection: The collection from which the elements are copied.</param> 
        /// <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception> 
        public ObservableRangeCollection(IEnumerable<T> collection)
            : base(collection) { }
    }
    

    更新 - 带有集合更改通知的可观察范围集合
    Imports System.Collections.Specialized
    Imports System.ComponentModel
    Imports System.Collections.ObjectModel
    
    Public Class ObservableRangeCollection(Of T) : Inherits ObservableCollection(Of T) : Implements INotifyCollectionChanging(Of T)
        ''' <summary>
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            MyBase.New()
        End Sub
    
        ''' <summary>
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
        ''' </summary>
        ''' <param name="collection">collection: The collection from which the elements are copied.</param>
        ''' <exception cref="System.ArgumentNullException">The collection parameter cannot be null.</exception>
        Public Sub New(ByVal collection As IEnumerable(Of T))
            MyBase.New(collection)
        End Sub
    
        ''' <summary>
        ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
        ''' </summary>
        Public Sub AddRange(ByVal collection As IEnumerable(Of T))
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, collection)
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            Dim index = Items.Count - 1
            For Each i In collection
                Items.Add(i)
            Next
    
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, index))
        End Sub
    
    
        ''' <summary>
        ''' Inserts the collection at specified index.
        ''' </summary>
        Public Sub InsertRange(ByVal index As Integer, ByVal Collection As IEnumerable(Of T))
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, Collection)
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            For Each i In Collection
                Items.Insert(index, i)
            Next
    
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub
    
    
        ''' <summary>
        ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
        ''' </summary>
        Public Sub RemoveRange(ByVal collection As IEnumerable(Of T))
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, collection)
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            For Each i In collection
                Items.Remove(i)
            Next
    
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub
    
    
    
        ''' <summary>
        ''' Clears the current collection and replaces it with the specified item.
        ''' </summary>
        Public Sub Replace(ByVal item As T)
            ReplaceRange(New T() {item})
        End Sub
    
        ''' <summary>
        ''' Clears the current collection and replaces it with the specified collection.
        ''' </summary>
        Public Sub ReplaceRange(ByVal collection As IEnumerable(Of T))
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items)
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            Items.Clear()
            For Each i In collection
                Items.Add(i)
            Next
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
        End Sub
    
        Protected Overrides Sub ClearItems()
            Dim e As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Reset, Items)
            OnCollectionChanging(e)
    
            If e.Cancel Then Exit Sub
    
            MyBase.ClearItems()
        End Sub
    
        Protected Overrides Sub InsertItem(ByVal index As Integer, ByVal item As T)
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Add, item)
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            MyBase.InsertItem(index, item)
        End Sub
    
        Protected Overrides Sub MoveItem(ByVal oldIndex As Integer, ByVal newIndex As Integer)
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)()
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            MyBase.MoveItem(oldIndex, newIndex)
        End Sub
    
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Remove, Items(index))
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            MyBase.RemoveItem(index)
        End Sub
    
        Protected Overrides Sub SetItem(ByVal index As Integer, ByVal item As T)
            Dim ce As New NotifyCollectionChangingEventArgs(Of T)(NotifyCollectionChangedAction.Replace, Items(index))
            OnCollectionChanging(ce)
            If ce.Cancel Then Exit Sub
    
            MyBase.SetItem(index, item)
        End Sub
    
        Protected Overrides Sub OnCollectionChanged(ByVal e As Specialized.NotifyCollectionChangedEventArgs)
            If e.NewItems IsNot Nothing Then
                For Each i As T In e.NewItems
                    If TypeOf i Is INotifyPropertyChanged Then AddHandler DirectCast(i, INotifyPropertyChanged).PropertyChanged, AddressOf Item_PropertyChanged
                Next
            End If
            MyBase.OnCollectionChanged(e)
        End Sub
    
        Private Sub Item_PropertyChanged(ByVal sender As T, ByVal e As ComponentModel.PropertyChangedEventArgs)
            OnCollectionChanged(New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset, sender, IndexOf(sender)))
        End Sub
    
        Public Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T)) Implements INotifyCollectionChanging(Of T).CollectionChanging
        Protected Overridable Sub OnCollectionChanging(ByVal e As NotifyCollectionChangingEventArgs(Of T))
            RaiseEvent CollectionChanging(Me, e)
        End Sub
    End Class
    
    
    Public Interface INotifyCollectionChanging(Of T)
        Event CollectionChanging(ByVal sender As Object, ByVal e As NotifyCollectionChangingEventArgs(Of T))
    End Interface
    
    Public Class NotifyCollectionChangingEventArgs(Of T) : Inherits CancelEventArgs
    
        Public Sub New()
            m_Action = NotifyCollectionChangedAction.Move
            m_Items = New T() {}
        End Sub
    
        Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal item As T)
            m_Action = action
            m_Items = New T() {item}
        End Sub
    
        Public Sub New(ByVal action As NotifyCollectionChangedAction, ByVal items As IEnumerable(Of T))
            m_Action = action
            m_Items = items
        End Sub
    
        Private m_Action As NotifyCollectionChangedAction
        Public ReadOnly Property Action() As NotifyCollectionChangedAction
            Get
                Return m_Action
            End Get
        End Property
    
        Private m_Items As IList
        Public ReadOnly Property Items() As IEnumerable(Of T)
            Get
                Return m_Items
            End Get
        End Property
    End Class
    

    【讨论】:

    • 当您迭代集合并添加/删除项目时,这是否仍会引发单个集合更改事件?
    • 斯科特·多尔曼是正确的。调用内置的 Add 方法将触发类似于每次添加的 3 个事件(用于项目计数的 NotifyPropertyChange、用于项目索引 [] 的 NotifyPropertyChange 和用于添加的项目的 NotifyCollectionChanged 事件)。要获得大型更新的最佳性能,需要完全替换 ObservableCollection。
    • @HiTechMagic Sc​​ott 不正确。因为这会在 Collection 的受保护项 IList 上调用 Add,所以没有发送此类更改的事件,我已经通过单元测试验证了使用 AddRange 对 CollectionChanged 事件的调用计数,我可以确认只是一个。
    • 需要在添加/删除/替换范围方法中调用OnPropertyChanged("Count");OnPropertyChanged("Item[]");来完全模仿标准的ObservableCollection。
    • C# 代码不起作用:附加信息:不支持范围操作。
    【解决方案2】:

    首先,请投票并评论 .NET repo 上的API request

    这是我对ObservableRangeCollection 的优化版本(James Montemagno 的one 的优化版本)。

    它的执行速度非常快,旨在尽可能重用现有元素并避免不必要的事件,或者尽可能将它们批处理为一个。 ReplaceRange 方法通过适当的索引替换/删除/添加所需的元素,并对可能的事件进行批处理。

    在 Xamarin.Forms UI 上测试,非常频繁地更新大型集合(每秒 5-7 次更新),效果很好。

    注意: 由于WPF不习惯使用范围操作,所以在WPF UI相关工作中从下方使用ObservableRangeCollection时会抛出NotSupportedException,比如绑定到ListBox等等(如果未绑定到 UI,您仍然可以使用 ObservableRangeCollection&lt;T&gt;)。
    但是,您可以使用WpfObservableRangeCollection&lt;T&gt; 解决方法。
    真正的解决方案是创建一个知道如何处理范围操作的CollectionView,但我仍然没有时间实现它。

    RAW Code - 以 Raw 格式打开,然后执行 Ctrl+A 以全选,然后执行 Ctrl +C 复制。

    // Licensed to the .NET Foundation under one or more agreements.
    // The .NET Foundation licenses this file to you under the MIT license.
    // See the LICENSE file in the project root for more information.
    
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Diagnostics;
    
    namespace System.Collections.ObjectModel
    {
      /// <summary>
      /// Implementation of a dynamic data collection based on generic Collection&lt;T&gt;,
      /// implementing INotifyCollectionChanged to notify listeners
      /// when items get added, removed or the whole list is refreshed.
      /// </summary>
      public class ObservableRangeCollection<T> : ObservableCollection<T>
      {
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
    
        #region Private Fields    
        [NonSerialized]
        private DeferredEventsCollection _deferredEvents;
        #endregion Private Fields
    
    
        //------------------------------------------------------
        //
        //  Constructors
        //
        //------------------------------------------------------
    
        #region Constructors
        /// <summary>
        /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
        /// </summary>
        public ObservableRangeCollection() { }
    
        /// <summary>
        /// Initializes a new instance of the ObservableCollection class that contains
        /// elements copied from the specified collection and has sufficient capacity
        /// to accommodate the number of elements copied.
        /// </summary>
        /// <param name="collection">The collection whose elements are copied to the new list.</param>
        /// <remarks>
        /// The elements are copied onto the ObservableCollection in the
        /// same order they are read by the enumerator of the collection.
        /// </remarks>
        /// <exception cref="ArgumentNullException"> collection is a null reference </exception>
        public ObservableRangeCollection(IEnumerable<T> collection) : base(collection) { }
    
        /// <summary>
        /// Initializes a new instance of the ObservableCollection class
        /// that contains elements copied from the specified list
        /// </summary>
        /// <param name="list">The list whose elements are copied to the new list.</param>
        /// <remarks>
        /// The elements are copied onto the ObservableCollection in the
        /// same order they are read by the enumerator of the list.
        /// </remarks>
        /// <exception cref="ArgumentNullException"> list is a null reference </exception>
        public ObservableRangeCollection(List<T> list) : base(list) { }
    
        #endregion Constructors
    
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
    
        #region Public Methods
    
        /// <summary>
        /// Adds the elements of the specified collection to the end of the <see cref="ObservableCollection{T}"/>.
        /// </summary>
        /// <param name="collection">
        /// The collection whose elements should be added to the end of the <see cref="ObservableCollection{T}"/>.
        /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
        /// </param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public void AddRange(IEnumerable<T> collection)
        {
          InsertRange(Count, collection);
        }
    
        /// <summary>
        /// Inserts the elements of a collection into the <see cref="ObservableCollection{T}"/> at the specified index.
        /// </summary>
        /// <param name="index">The zero-based index at which the new elements should be inserted.</param>
        /// <param name="collection">The collection whose elements should be inserted into the List<T>.
        /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.</param>                
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is not in the collection range.</exception>
        public void InsertRange(int index, IEnumerable<T> collection)
        {
          if (collection == null)
            throw new ArgumentNullException(nameof(collection));
          if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
          if (index > Count)
            throw new ArgumentOutOfRangeException(nameof(index));
    
          if (collection is ICollection<T> countable)
          {
            if (countable.Count == 0)
            {
              return;
            }
          }
          else if (!ContainsAny(collection))
          {
            return;
          }
    
          CheckReentrancy();
    
          //expand the following couple of lines when adding more constructors.
          var target = (List<T>)Items;
          target.InsertRange(index, collection);
    
          OnEssentialPropertiesChanged();
    
          if (!(collection is IList list))
            list = new List<T>(collection);
    
          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
        }
    
    
        /// <summary> 
        /// Removes the first occurence of each item in the specified collection from the <see cref="ObservableCollection{T}"/>.
        /// </summary>
        /// <param name="collection">The items to remove.</param>        
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public void RemoveRange(IEnumerable<T> collection)
        {
          if (collection == null)
            throw new ArgumentNullException(nameof(collection));
    
          if (Count == 0)
          {
            return;
          }
          else if (collection is ICollection<T> countable)
          {
            if (countable.Count == 0)
              return;
            else if (countable.Count == 1)
              using (IEnumerator<T> enumerator = countable.GetEnumerator())
              {
                enumerator.MoveNext();
                Remove(enumerator.Current);
                return;
              }
          }
          else if (!(ContainsAny(collection)))
          {
            return;
          }
    
          CheckReentrancy();
    
          var clusters = new Dictionary<int, List<T>>();
          var lastIndex = -1;
          List<T> lastCluster = null;
          foreach (T item in collection)
          {
            var index = IndexOf(item);
            if (index < 0)
            {
              continue;
            }
    
            Items.RemoveAt(index);
    
            if (lastIndex == index && lastCluster != null)
            {
              lastCluster.Add(item);
            }
            else
            {
              clusters[lastIndex = index] = lastCluster = new List<T> { item };
            }
          }
    
          OnEssentialPropertiesChanged();
    
          if (Count == 0)
            OnCollectionReset();
          else
            foreach (KeyValuePair<int, List<T>> cluster in clusters)
              OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));
    
        }
    
        /// <summary>
        /// Iterates over the collection and removes all items that satisfy the specified match.
        /// </summary>
        /// <remarks>The complexity is O(n).</remarks>
        /// <param name="match"></param>
        /// <returns>Returns the number of elements that where </returns>
        /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
        public int RemoveAll(Predicate<T> match)
        {
          return RemoveAll(0, Count, match);
        }
    
        /// <summary>
        /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
        /// </summary>
        /// <remarks>The complexity is O(n).</remarks>
        /// <param name="index">The index of where to start performing the search.</param>
        /// <param name="count">The number of items to iterate on.</param>
        /// <param name="match"></param>
        /// <returns>Returns the number of elements that where </returns>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="match"/> is null.</exception>
        public int RemoveAll(int index, int count, Predicate<T> match)
        {
          if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
          if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
          if (index + count > Count)
            throw new ArgumentOutOfRangeException(nameof(index));
          if (match == null)
            throw new ArgumentNullException(nameof(match));
    
          if (Count == 0)
            return 0;
    
          List<T> cluster = null;
          var clusterIndex = -1;
          var removedCount = 0;
    
          using (BlockReentrancy())
          using (DeferEvents())
          {
            for (var i = 0; i < count; i++, index++)
            {
              T item = Items[index];
              if (match(item))
              {
                Items.RemoveAt(index);
                removedCount++;
    
                if (clusterIndex == index)
                {
                  Debug.Assert(cluster != null);
                  cluster.Add(item);
                }
                else
                {
                  cluster = new List<T> { item };
                  clusterIndex = index;
                }
    
                index--;
              }
              else if (clusterIndex > -1)
              {
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
                clusterIndex = -1;
                cluster = null;
              }
            }
    
            if (clusterIndex > -1)
              OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster, clusterIndex));
          }
    
          if (removedCount > 0)
            OnEssentialPropertiesChanged();
    
          return removedCount;
        }
    
        /// <summary>
        /// Removes a range of elements from the <see cref="ObservableCollection{T}"/>>.
        /// </summary>
        /// <param name="index">The zero-based starting index of the range of elements to remove.</param>
        /// <param name="count">The number of elements to remove.</param>
        /// <exception cref="ArgumentOutOfRangeException">The specified range is exceeding the collection.</exception>
        public void RemoveRange(int index, int count)
        {
          if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
          if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
          if (index + count > Count)
            throw new ArgumentOutOfRangeException(nameof(index));
    
          if (count == 0)
            return;
    
          if (count == 1)
          {
            RemoveItem(index);
            return;
          }
    
          //Items will always be List<T>, see constructors
          var items = (List<T>)Items;
          List<T> removedItems = items.GetRange(index, count);
    
          CheckReentrancy();
    
          items.RemoveRange(index, count);
    
          OnEssentialPropertiesChanged();
    
          if (Count == 0)
            OnCollectionReset();
          else
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index));
        }
    
        /// <summary> 
        /// Clears the current collection and replaces it with the specified collection,
        /// using the default <see cref="EqualityComparer{T}"/>.
        /// </summary>             
        /// <param name="collection">The items to fill the collection with, after clearing it.</param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public void ReplaceRange(IEnumerable<T> collection)
        {
          ReplaceRange(0, Count, collection, EqualityComparer<T>.Default);
        }
    
        /// <summary>
        /// Clears the current collection and replaces it with the specified collection,
        /// using the specified comparer to skip equal items.
        /// </summary>
        /// <param name="collection">The items to fill the collection with, after clearing it.</param>
        /// <param name="comparer">An <see cref="IEqualityComparer{T}"/> to be used
        /// to check whether an item in the same location already existed before,
        /// which in case it would not be added to the collection, and no event will be raised for it.</param>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
        public void ReplaceRange(IEnumerable<T> collection, IEqualityComparer<T> comparer)
        {
          ReplaceRange(0, Count, collection, comparer);
        }
    
        /// <summary>
        /// Removes the specified range and inserts the specified collection,
        /// ignoring equal items (using <see cref="EqualityComparer{T}.Default"/>).
        /// </summary>
        /// <param name="index">The index of where to start the replacement.</param>
        /// <param name="count">The number of items to be replaced.</param>
        /// <param name="collection">The collection to insert in that location.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        public void ReplaceRange(int index, int count, IEnumerable<T> collection)
        {
          ReplaceRange(index, count, collection, EqualityComparer<T>.Default);
        }
    
        /// <summary>
        /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
        /// </summary>
        /// <param name="index">The index of where to start the replacement.</param>
        /// <param name="count">The number of items to be replaced.</param>
        /// <param name="collection">The collection to insert in that location.</param>
        /// <param name="comparer">The comparer to use when checking for equal items.</param>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
        /// <exception cref="ArgumentOutOfRangeException"><paramref name="count"/> is out of range.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="collection"/> is null.</exception>
        /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is null.</exception>
        public void ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer)
        {
          if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
          if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
          if (index + count > Count)
            throw new ArgumentOutOfRangeException(nameof(index));
    
          if (collection == null)
            throw new ArgumentNullException(nameof(collection));
          if (comparer == null)
            throw new ArgumentNullException(nameof(comparer));
    
          if (collection is ICollection<T> countable)
          {
            if (countable.Count == 0)
            {
              RemoveRange(index, count);
              return;
            }
          }
          else if (!ContainsAny(collection))
          {
            RemoveRange(index, count);
            return;
          }
    
          if (index + count == 0)
          {
            InsertRange(0, collection);
            return;
          }
    
          if (!(collection is IList<T> list))
            list = new List<T>(collection);
    
          using (BlockReentrancy())
          using (DeferEvents())
          {
            var rangeCount = index + count;
            var addedCount = list.Count;
    
            var changesMade = false;
            List<T>
                newCluster = null,
                oldCluster = null;
    
    
            int i = index;
            for (; i < rangeCount && i - index < addedCount; i++)
            {
              //parallel position
              T old = this[i], @new = list[i - index];
              if (comparer.Equals(old, @new))
              {
                OnRangeReplaced(i, newCluster, oldCluster);
                continue;
              }
              else
              {
                Items[i] = @new;
    
                if (newCluster == null)
                {
                  Debug.Assert(oldCluster == null);
                  newCluster = new List<T> { @new };
                  oldCluster = new List<T> { old };
                }
                else
                {
                  newCluster.Add(@new);
                  oldCluster.Add(old);
                }
    
                changesMade = true;
              }
            }
    
            OnRangeReplaced(i, newCluster, oldCluster);
    
            //exceeding position
            if (count != addedCount)
            {
              var items = (List<T>)Items;
              if (count > addedCount)
              {
                var removedCount = rangeCount - addedCount;
                T[] removed = new T[removedCount];
                items.CopyTo(i, removed, 0, removed.Length);
                items.RemoveRange(i, removedCount);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed, i));
              }
              else
              {
                var k = i - index;
                T[] added = new T[addedCount - k];
                for (int j = k; j < addedCount; j++)
                {
                  T @new = list[j];
                  added[j - k] = @new;
                }
                items.InsertRange(i, added);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, i));
              }
    
              OnEssentialPropertiesChanged();
            }
            else if (changesMade)
            {
              OnIndexerPropertyChanged();
            }
          }
        }
    
        #endregion Public Methods
    
    
        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------
    
        #region Protected Methods
    
        /// <summary>
        /// Called by base class Collection&lt;T&gt; when the list is being cleared;
        /// raises a CollectionChanged event to any listeners.
        /// </summary>
        protected override void ClearItems()
        {
          if (Count == 0)
            return;
    
          CheckReentrancy();
          base.ClearItems();
          OnEssentialPropertiesChanged();
          OnCollectionReset();
        }
    
        /// <summary>
        /// Called by base class Collection&lt;T&gt; when an item is set in list;
        /// raises a CollectionChanged event to any listeners.
        /// </summary>
        protected override void SetItem(int index, T item)
        {
          if (Equals(this[index], item))
            return;
    
          CheckReentrancy();
          T originalItem = this[index];
          base.SetItem(index, item);
    
          OnIndexerPropertyChanged();
          OnCollectionChanged(NotifyCollectionChangedAction.Replace, originalItem, item, index);
        }
    
        /// <summary>
        /// Raise CollectionChanged event to any listeners.
        /// Properties/methods modifying this ObservableCollection will raise
        /// a collection changed event through this virtual method.
        /// </summary>
        /// <remarks>
        /// When overriding this method, either call its base implementation
        /// or call <see cref="BlockReentrancy"/> to guard against reentrant collection changes.
        /// </remarks>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
          if (_deferredEvents != null)
          {
            _deferredEvents.Add(e);
            return;
          }
          base.OnCollectionChanged(e);
        }
    
        protected virtual IDisposable DeferEvents() => new DeferredEventsCollection(this);
    
        #endregion Protected Methods
    
    
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
    
        #region Private Methods
    
        /// <summary>
        /// Helper function to determine if a collection contains any elements.
        /// </summary>
        /// <param name="collection">The collection to evaluate.</param>
        /// <returns></returns>
        private static bool ContainsAny(IEnumerable<T> collection)
        {
          using (IEnumerator<T> enumerator = collection.GetEnumerator())
            return enumerator.MoveNext();
        }
    
        /// <summary>
        /// Helper to raise Count property and the Indexer property.
        /// </summary>
        private void OnEssentialPropertiesChanged()
        {
          OnPropertyChanged(EventArgsCache.CountPropertyChanged);
          OnIndexerPropertyChanged();
        }
    
        /// <summary>
        /// /// Helper to raise a PropertyChanged event for the Indexer property
        /// /// </summary>
        private void OnIndexerPropertyChanged() =>
          OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);
    
        /// <summary>
        /// Helper to raise CollectionChanged event to any listeners
        /// </summary>
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
          OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));
    
        /// <summary>
        /// Helper to raise CollectionChanged event with action == Reset to any listeners
        /// </summary>
        private void OnCollectionReset() =>
          OnCollectionChanged(EventArgsCache.ResetCollectionChanged);
    
        /// <summary>
        /// Helper to raise event for clustered action and clear cluster.
        /// </summary>
        /// <param name="followingItemIndex">The index of the item following the replacement block.</param>
        /// <param name="newCluster"></param>
        /// <param name="oldCluster"></param>
        //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable<T> collection, IEqualityComparer<T> comparer),
        //move when supported language version updated.
        private void OnRangeReplaced(int followingItemIndex, ICollection<T> newCluster, ICollection<T> oldCluster)
        {
          if (oldCluster == null || oldCluster.Count == 0)
          {
            Debug.Assert(newCluster == null || newCluster.Count == 0);
            return;
          }
    
          OnCollectionChanged(
              new NotifyCollectionChangedEventArgs(
                  NotifyCollectionChangedAction.Replace,
                  new List<T>(newCluster),
                  new List<T>(oldCluster),
                  followingItemIndex - oldCluster.Count));
    
          oldCluster.Clear();
          newCluster.Clear();
        }
    
        #endregion Private Methods
    
        //------------------------------------------------------
        //
        //  Private Types
        //
        //------------------------------------------------------
    
        #region Private Types
        private sealed class DeferredEventsCollection : List<NotifyCollectionChangedEventArgs>, IDisposable
        {
          private readonly ObservableRangeCollection<T> _collection;
          public DeferredEventsCollection(ObservableRangeCollection<T> collection)
          {
            Debug.Assert(collection != null);
            Debug.Assert(collection._deferredEvents == null);
            _collection = collection;
            _collection._deferredEvents = this;
          }
    
          public void Dispose()
          {
            _collection._deferredEvents = null;
            foreach (var args in this)
              _collection.OnCollectionChanged(args);
          }
        }
    
        #endregion Private Types
    
      }
    
      /// <remarks>
      /// To be kept outside <see cref="ObservableCollection{T}"/>, since otherwise, a new instance will be created for each generic type used.
      /// </remarks>
      internal static class EventArgsCache
      {
        internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs("Count");
        internal static readonly PropertyChangedEventArgs IndexerPropertyChanged = new PropertyChangedEventArgs("Item[]");
        internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
      }
    }
    

    【讨论】:

    • 你能帮我处理这段代码吗?也许我使用了错误版本的框架,但例如在 AddRange 方法的第 48 行有“if (collection is ICollection list)” - 但是这个“list”对象是什么?
    • 是否可以在 WPF 应用程序中使用它?我正在尝试这样做,但得到“不支持范围操作”。例外。
    • System.NotSupportedException:不支持范围操作。 dot net 4.5 vs 2017
    • @HenkaProgrammer 我已经更新了我的答案。在 WPF 中使用 this.
    【解决方案3】:

    我认为 AddRange 最好这样实现:

    public void AddRange(IEnumerable<T> collection)
    {
        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(
            new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    

    它会为您保存一份列表副本。此外,如果您想进行微优化,您最多可以添加 N 个项目,如果添加的项目超过 N 个,则进行重置。

    【讨论】:

    【解决方案4】:

    您必须小心地将 UI 绑定到您的自定义集合 - Default CollectionView 类仅支持单个项目通知。

    【讨论】:

      【解决方案5】:

      需要OnPropertyChanged("Count")OnPropertyChanged("Item[]") 调用的证明,以便按照ObservableCollection 行事。请注意,如果您不打扰,我不知道会有什么后果!

      这是一个测试方法,它表明在普通的 observable 集合中,每个 add 都有两个 PropertyChange 事件。一种用于"Count",一种用于"Item[]"

      [TestMethod]
      public void TestAddSinglesInOldObsevableCollection()
      {
        int colChangedEvents = 0;
        int propChangedEvents = 0;
        var collection = new ObservableCollection<object>();
        collection.CollectionChanged += (sender, e) => { colChangedEvents++; };
        (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; };
        collection.Add(new object());
        collection.Add(new object());
        collection.Add(new object());
        Assert.AreEqual(3, colChangedEvents);
        Assert.AreEqual(6, propChangedEvents);
      }
      

      @Shimmy ,为您的收藏交换标准并更改为添加范围,您将获得零 PropertyChanges。请注意,集合更改确实可以正常工作,但不能完全按照 ObservableCollection 所做的。所以测试 shimmy 集合看起来像这样:

      [TestMethod]
      public void TestShimmyAddRange()
      {
        int colChangedEvents = 0;
        int propChangedEvents = 0;
        var collection = new ShimmyObservableCollection<object>();
        collection.CollectionChanged += (sender, e) => { colChangedEvents++; };
        (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propChangedEvents++; };
        collection.AddRange(new[]{
          new object(), new object(), new object(), new object()}); //4 objects at once
        Assert.AreEqual(1, colChangedEvents);  //great, just one!
        Assert.AreEqual(2, propChangedEvents); //fails, no events :(
      }
      

      仅供参考,这里是来自 ObservableCollection 的 InsertItem(也由 Add 调用)的代码:

      protected override void InsertItem(int index, T item)
      {
        base.CheckReentrancy();
        base.InsertItem(index, item);
        base.OnPropertyChanged("Count");
        base.OnPropertyChanged("Item[]");
        base.OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
      }
      

      【讨论】:

        【解决方案6】:

        由于在 ObservableCollection 上可能需要执行许多操作,例如先清除然后添加范围,然后为组合框插入“全部”项,因此我最终得到了以下解决方案:

        public static class LinqExtensions
        {
            public static ICollection<T> AddRange<T>(this ICollection<T> source, IEnumerable<T> addSource)
            {
                foreach(T item in addSource)
                {
                    source.Add(item);
                }
        
                return source;
            }
        }
        
        public class ExtendedObservableCollection<T>: ObservableCollection<T>
        {
            public void Execute(Action<IList<T>> itemsAction)
            {
                itemsAction(Items);
                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }
        

        以及如何使用它的示例:

        MyDogs.Execute(items =>
        {
            items.Clear();
            items.AddRange(Context.Dogs);
            items.Insert(0, new Dog { Id = 0, Name = "All Dogs" });
        });
        

        在 Execute 处理完底层列表后,只会调用一次 Reset 通知。

        【讨论】:

          【解决方案7】:

          C# 总结的后代。

          更多阅读:http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

          public sealed class ObservableCollectionEx<T> : ObservableCollection<T>
          {
              #region Ctor
          
              public ObservableCollectionEx()
              {
              }
          
              public ObservableCollectionEx(List<T> list) : base(list)
              {
              }
          
              public ObservableCollectionEx(IEnumerable<T> collection) : base(collection)
              {
              }
          
              #endregion
          
              /// <summary> 
              /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
              /// </summary> 
              public void AddRange(
                  IEnumerable<T> itemsToAdd,
                  ECollectionChangeNotificationMode notificationMode = ECollectionChangeNotificationMode.Add)
              {
                  if (itemsToAdd == null)
                  {
                      throw new ArgumentNullException("itemsToAdd");
                  }
                  CheckReentrancy();
          
                  if (notificationMode == ECollectionChangeNotificationMode.Reset)
                  {
                      foreach (var i in itemsToAdd)
                      {
                          Items.Add(i);
                      }
          
                      OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                      OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
          
                      return;
                  }
          
                  int startIndex = Count;
                  var changedItems = itemsToAdd is List<T> ? (List<T>) itemsToAdd : new List<T>(itemsToAdd);
                  foreach (var i in changedItems)
                  {
                      Items.Add(i);
                  }
          
                  OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                  OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                  OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems, startIndex));
              }
          
              public enum ECollectionChangeNotificationMode
              {
                  /// <summary>
                  /// Notifies that only a portion of data was changed and supplies the changed items (not supported by some elements,
                  /// like CollectionView class).
                  /// </summary>
                  Add,
          
                  /// <summary>
                  /// Notifies that the entire collection was changed, does not supply the changed items (may be inneficient with large
                  /// collections as requires the full update even if a small portion of items was added).
                  /// </summary>
                  Reset
              }
          }
          

          【讨论】:

          • 正如您链接到的博客所提到的,ListCollectionView(可能还有其他)不支持多项目更改事件。您必须在此处使用重置操作而不是添加。
          【解决方案8】:

          是的,添加您自己的自定义 Observable 集合就足够公平了。不要忘记引发适当的事件,无论它目前是否被 UI 使用;)您将必须引发“Item []”属性(WPF 侧和绑定控件需要)以及 NotifyCollectionChangedEventArgs 的属性更改通知添加了一组项目(您的范围)。 我做过这样的事情(以及排序支持和其他一些事情),并且在演示层和代码隐藏层方面都没有问题。

          【讨论】:

            【解决方案9】:

            Here 是对集合更改和 UI 问题的一些额外帮助:

            【讨论】:

            • 虽然理论上这可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。
            • 链接引用是支持 AddRange() 并且是线程安全的自定义可观察列表实现的好资源。
            【解决方案10】:

            ObservableRangeCollection 应该通过类似的测试

            [Test]
            public void TestAddRangeWhileBoundToListCollectionView()
            {
                int collectionChangedEventsCounter = 0;
                int propertyChangedEventsCounter = 0;
                var collection = new ObservableRangeCollection<object>();
            
                collection.CollectionChanged += (sender, e) => { collectionChangedEventsCounter++; };
                (collection as INotifyPropertyChanged).PropertyChanged += (sender, e) => { propertyChangedEventsCounter++; };
            
                var list = new ListCollectionView(collection);
            
                collection.AddRange(new[] { new object(), new object(), new object(), new object() });
            
                Assert.AreEqual(4, collection.Count);
                Assert.AreEqual(1, collectionChangedEventsCounter);
                Assert.AreEqual(2, propertyChangedEventsCounter);
            }
            

            否则我们得到

            System.NotSupportedException : Range actions are not supported.
            

            与控件一起使用时。

            我没有看到理想的解决方案,但 NotifyCollectionChangedAction.Reset 而不是 Add/Remove 可以部分解决问题。请参阅 net_prog 提到的http://blogs.msdn.com/b/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

            【讨论】:

              【解决方案11】:

              这是一个简单的 observablecollection,它在 AddRange 方法结束时发出通知,基于这篇文章 https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/

              它也是异步的并且可以跨线程修改,基于这篇文章https://thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/

              public class ConcurrentObservableCollection<T> : ObservableCollection<T>
              {
                  private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
              
                  private bool _suppressNotification = false;
              
                  public ConcurrentObservableCollection()
                      : base()
                  {
                  }
                  public ConcurrentObservableCollection(IEnumerable<T> list)
                      : base(list)
                  {
                  }
              
                  public void AddRange(IEnumerable<T> collection)
                  {
                      if (collection != null)
                      {
                          _suppressNotification = true;
                          foreach (var item in collection)
                          {
                              this.Add(item);
                          }
                          _suppressNotification = false;
              
                          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                      }
                  }
                  public void RemoveRange(IEnumerable<T> collection)
                  {
                      if (collection != null)
                      {
                          _suppressNotification = true;
                          foreach (var item in collection)
                          {
                              this.Remove(item);
                          }
                          _suppressNotification = false;
              
                          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                      }
                  }
              
                  protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
                  {
                      if (SynchronizationContext.Current == _synchronizationContext)
                      {
                          // Execute the CollectionChanged event on the current thread
                          RaiseCollectionChanged(e);
                      }
                      else
                      {
                          // Raises the CollectionChanged event on the creator thread
                          _synchronizationContext.Send(RaiseCollectionChanged, e);
                      }
                  }
                  protected override void OnPropertyChanged(PropertyChangedEventArgs e)
                  {
                      if (SynchronizationContext.Current == _synchronizationContext)
                      {
                          // Execute the PropertyChanged event on the current thread
                          RaisePropertyChanged(e);
                      }
                      else
                      {
                          // Raises the PropertyChanged event on the creator thread
                          _synchronizationContext.Send(RaisePropertyChanged, e);
                      }
                  }
              
                  private void RaiseCollectionChanged(object param)
                  {
                      // We are in the creator thread, call the base implementation directly
                      if (!_suppressNotification)
                          base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
                  }
                  private void RaisePropertyChanged(object param)
                  {
                      // We are in the creator thread, call the base implementation directly
                      base.OnPropertyChanged((PropertyChangedEventArgs)param);
                  }
              }
              

              【讨论】:

                【解决方案12】:

                这是对已接受答案的修改,以提供更多功能。

                RangeCollection.cs:

                public class RangeCollection<T> : ObservableCollection<T>
                {
                    #region Members
                
                    /// <summary>
                    /// Occurs when a single item is added.
                    /// </summary>
                    public event EventHandler<ItemAddedEventArgs<T>> ItemAdded;
                
                    /// <summary>
                    /// Occurs when a single item is inserted.
                    /// </summary>
                    public event EventHandler<ItemInsertedEventArgs<T>> ItemInserted;
                
                    /// <summary>
                    /// Occurs when a single item is removed.
                    /// </summary>
                    public event EventHandler<ItemRemovedEventArgs<T>> ItemRemoved;
                
                    /// <summary>
                    /// Occurs when a single item is replaced.
                    /// </summary>
                    public event EventHandler<ItemReplacedEventArgs<T>> ItemReplaced;
                
                    /// <summary>
                    /// Occurs when items are added to this.
                    /// </summary>
                    public event EventHandler<ItemsAddedEventArgs<T>> ItemsAdded;
                
                    /// <summary>
                    /// Occurs when items are removed from this.
                    /// </summary>
                    public event EventHandler<ItemsRemovedEventArgs<T>> ItemsRemoved;
                
                    /// <summary>
                    /// Occurs when items are replaced within this.
                    /// </summary>
                    public event EventHandler<ItemsReplacedEventArgs<T>> ItemsReplaced;
                
                    /// <summary>
                    /// Occurs when entire collection is cleared.
                    /// </summary>
                    public event EventHandler<ItemsClearedEventArgs<T>> ItemsCleared;
                
                    /// <summary>
                    /// Occurs when entire collection is replaced.
                    /// </summary>
                    public event EventHandler<CollectionReplacedEventArgs<T>> CollectionReplaced;
                
                    #endregion
                
                    #region Helper Methods
                
                    /// <summary>
                    /// Throws exception if any of the specified objects are null.
                    /// </summary>
                    private void Check(params T[] Items)
                    {
                        foreach (T Item in Items)
                        {
                            if (Item == null)
                            {
                                throw new ArgumentNullException("Item cannot be null.");
                            }
                        }
                    }
                
                    private void Check(IEnumerable<T> Items)
                    {
                        if (Items == null) throw new ArgumentNullException("Items cannot be null.");
                    }
                
                    private void Check(IEnumerable<IEnumerable<T>> Items)
                    {
                        if (Items == null) throw new ArgumentNullException("Items cannot be null.");
                    }
                
                    private void RaiseChanged(NotifyCollectionChangedAction Action)
                    {
                        this.OnPropertyChanged(new PropertyChangedEventArgs("Count"));
                        this.OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
                        this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    }
                
                    #endregion
                
                    #region Bulk Methods
                
                    /// <summary> 
                    /// Adds the elements of the specified collection to the end of this.
                    /// </summary> 
                    public void AddRange(IEnumerable<T> NewItems)
                    {
                        this.Check(NewItems);
                        foreach (var i in NewItems) this.Items.Add(i);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        this.OnItemsAdded(new ItemsAddedEventArgs<T>(NewItems));
                    }
                
                    /// <summary>
                    /// Adds variable IEnumerable<T> to this.
                    /// </summary>
                    /// <param name="List"></param>
                    public void AddRange(params IEnumerable<T>[] NewItems)
                    {
                        this.Check(NewItems);
                        foreach (IEnumerable<T> Items in NewItems) foreach (T Item in Items) this.Items.Add(Item);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        //TO-DO: Raise OnItemsAdded with combined IEnumerable<T>.
                    }
                
                    /// <summary> 
                    /// Removes the first occurence of each item in the specified collection. 
                    /// </summary> 
                    public void Remove(IEnumerable<T> OldItems)
                    {
                        this.Check(OldItems);
                        foreach (var i in OldItems) Items.Remove(i);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        OnItemsRemoved(new ItemsRemovedEventArgs<T>(OldItems));
                    }
                
                    /// <summary>
                    /// Removes all occurences of each item in the specified collection.
                    /// </summary>
                    /// <param name="itemsToRemove"></param>
                    public void RemoveAll(IEnumerable<T> OldItems)
                    {
                        this.Check(OldItems);
                        var set = new HashSet<T>(OldItems);
                        var list = this as List<T>;
                        int i = 0;
                        while (i < this.Count) if (set.Contains(this[i])) this.RemoveAt(i); else i++;
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        OnItemsRemoved(new ItemsRemovedEventArgs<T>(OldItems));
                    }
                
                    /// <summary> 
                    /// Replaces all occurences of a single item with specified item.
                    /// </summary> 
                    public void ReplaceAll(T Old, T New)
                    {
                        this.Check(Old, New);
                        this.Replace(Old, New, false);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        this.OnItemReplaced(new ItemReplacedEventArgs<T>(Old, New));
                    }
                
                    /// <summary> 
                    /// Clears this and adds specified collection. 
                    /// </summary> 
                    public void ReplaceCollection(IEnumerable<T> NewItems, bool SupressEvent = false)
                    {
                        this.Check(NewItems);
                        IEnumerable<T> OldItems = new List<T>(this.Items);
                        this.Items.Clear();
                        foreach (T Item in NewItems) this.Items.Add(Item);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        this.OnReplaced(new CollectionReplacedEventArgs<T>(OldItems, NewItems));
                    }
                
                    private void Replace(T Old, T New, bool BreakFirst)
                    {
                        List<T> Cloned = new List<T>(this.Items);
                        int i = 0;
                        foreach (T Item in Cloned)
                        {
                            if (Item.Equals(Old))
                            {
                                this.Items.Remove(Item);
                                this.Items.Insert(i, New);
                                if (BreakFirst) break;
                            }
                            i++;
                        }
                    }
                
                    /// <summary> 
                    /// Replaces the first occurence of a single item with specified item.
                    /// </summary> 
                    public void Replace(T Old, T New)
                    {
                        this.Check(Old, New);
                        this.Replace(Old, New, true);
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        this.OnItemReplaced(new ItemReplacedEventArgs<T>(Old, New));
                    }
                
                    #endregion
                
                    #region  New Methods
                
                    /// <summary>
                    /// Removes a single item.
                    /// </summary>
                    /// <param name="Item"></param>
                    public new void Remove(T Item)
                    {
                        this.Check(Item);
                        base.Remove(Item);
                        OnItemRemoved(new ItemRemovedEventArgs<T>(Item));
                    }
                
                    /// <summary>
                    /// Removes a single item at specified index.
                    /// </summary>
                    /// <param name="i"></param>
                    public new void RemoveAt(int i)
                    {
                        T OldItem = this.Items[i]; //This will throw first if null
                        base.RemoveAt(i);
                        OnItemRemoved(new ItemRemovedEventArgs<T>(OldItem));
                    }
                
                    /// <summary>
                    /// Clears this.
                    /// </summary>
                    public new void Clear()
                    {
                        IEnumerable<T> OldItems = new List<T>(this.Items);
                        this.Items.Clear();
                        this.RaiseChanged(NotifyCollectionChangedAction.Reset);
                        this.OnCleared(new ItemsClearedEventArgs<T>(OldItems));
                    }
                
                    /// <summary>
                    /// Adds a single item to end of this.
                    /// </summary>
                    /// <param name="t"></param>
                    public new void Add(T Item)
                    {
                        this.Check(Item);
                        base.Add(Item);
                        this.OnItemAdded(new ItemAddedEventArgs<T>(Item));
                    }
                
                    /// <summary>
                    /// Inserts a single item at specified index.
                    /// </summary>
                    /// <param name="i"></param>
                    /// <param name="t"></param>
                    public new void Insert(int i, T Item)
                    {
                        this.Check(Item);
                        base.Insert(i, Item);
                        this.OnItemInserted(new ItemInsertedEventArgs<T>(Item, i));
                    }
                
                    /// <summary>
                    /// Returns list of T.ToString().
                    /// </summary>
                    /// <returns></returns>
                    public new IEnumerable<string> ToString()
                    {
                        foreach (T Item in this) yield return Item.ToString();
                    }
                
                    #endregion
                
                    #region Event Methods
                
                    private void OnItemAdded(ItemAddedEventArgs<T> i)
                    {
                        if (this.ItemAdded != null) this.ItemAdded(this, new ItemAddedEventArgs<T>(i.NewItem));
                    }
                
                    private void OnItemInserted(ItemInsertedEventArgs<T> i)
                    {
                        if (this.ItemInserted != null) this.ItemInserted(this, new ItemInsertedEventArgs<T>(i.NewItem, i.Index));
                    }
                
                    private void OnItemRemoved(ItemRemovedEventArgs<T> i)
                    {
                        if (this.ItemRemoved != null) this.ItemRemoved(this, new ItemRemovedEventArgs<T>(i.OldItem));
                    }
                
                    private void OnItemReplaced(ItemReplacedEventArgs<T> i)
                    {
                        if (this.ItemReplaced != null) this.ItemReplaced(this, new ItemReplacedEventArgs<T>(i.OldItem, i.NewItem));
                    }
                
                    private void OnItemsAdded(ItemsAddedEventArgs<T> i)
                    {
                        if (this.ItemsAdded != null) this.ItemsAdded(this, new ItemsAddedEventArgs<T>(i.NewItems));
                    }
                
                    private void OnItemsRemoved(ItemsRemovedEventArgs<T> i)
                    {
                        if (this.ItemsRemoved != null) this.ItemsRemoved(this, new ItemsRemovedEventArgs<T>(i.OldItems));
                    }
                
                    private void OnItemsReplaced(ItemsReplacedEventArgs<T> i)
                    {
                        if (this.ItemsReplaced != null) this.ItemsReplaced(this, new ItemsReplacedEventArgs<T>(i.OldItems, i.NewItems));
                    }
                
                    private void OnCleared(ItemsClearedEventArgs<T> i)
                    {
                        if (this.ItemsCleared != null) this.ItemsCleared(this, new ItemsClearedEventArgs<T>(i.OldItems));
                    }
                
                    private void OnReplaced(CollectionReplacedEventArgs<T> i)
                    {
                        if (this.CollectionReplaced != null) this.CollectionReplaced(this, new CollectionReplacedEventArgs<T>(i.OldItems, i.NewItems));
                    }
                
                    #endregion
                
                    #region RangeCollection
                
                    /// <summary> 
                    /// Initializes a new instance. 
                    /// </summary> 
                    public RangeCollection() : base() { }
                
                    /// <summary> 
                    /// Initializes a new instance from specified enumerable. 
                    /// </summary> 
                    public RangeCollection(IEnumerable<T> Collection) : base(Collection) { }
                
                    /// <summary> 
                    /// Initializes a new instance from specified list.
                    /// </summary> 
                    public RangeCollection(List<T> List) : base(List) { }
                
                    /// <summary>
                    /// Initializes a new instance with variable T.
                    /// </summary>
                    public RangeCollection(params T[] Items) : base()
                    {
                        this.AddRange(Items);
                    }
                
                    /// <summary>
                    /// Initializes a new instance with variable enumerable.
                    /// </summary>
                    public RangeCollection(params IEnumerable<T>[] Items) : base()
                    {
                        this.AddRange(Items);
                    }
                
                    #endregion
                }
                

                事件类:

                public class CollectionReplacedEventArgs<T> : ReplacedEventArgs<T>
                {
                    public CollectionReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New) : base(Old, New) { }
                }
                
                public class ItemAddedEventArgs<T> : EventArgs
                {
                    public T NewItem;
                    public ItemAddedEventArgs(T t)
                    {
                        this.NewItem = t;
                    }
                }
                
                public class ItemInsertedEventArgs<T> : EventArgs
                {
                    public int Index;
                    public T NewItem;
                    public ItemInsertedEventArgs(T t, int i)
                    {
                        this.NewItem = t;
                        this.Index = i;
                    }
                }
                
                public class ItemRemovedEventArgs<T> : EventArgs
                {
                    public T OldItem;
                    public ItemRemovedEventArgs(T t)
                    {
                        this.OldItem = t;
                    }
                }
                
                public class ItemReplacedEventArgs<T> : EventArgs
                {
                    public T OldItem;
                    public T NewItem;
                    public ItemReplacedEventArgs(T Old, T New)
                    {
                        this.OldItem = Old;
                        this.NewItem = New;
                    }
                }
                
                public class ItemsAddedEventArgs<T> : EventArgs
                {
                    public IEnumerable<T> NewItems;
                    public ItemsAddedEventArgs(IEnumerable<T> t)
                    {
                        this.NewItems = t;
                    }
                }
                
                public class ItemsClearedEventArgs<T> : RemovedEventArgs<T>
                {
                    public ItemsClearedEventArgs(IEnumerable<T> Old) : base(Old) { }
                }
                
                public class ItemsRemovedEventArgs<T> : RemovedEventArgs<T>
                {
                    public ItemsRemovedEventArgs(IEnumerable<T> Old) : base(Old) { }
                }
                
                public class ItemsReplacedEventArgs<T> : ReplacedEventArgs<T>
                {
                    public ItemsReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New) : base(Old, New) { }
                }
                
                public class RemovedEventArgs<T> : EventArgs
                {
                    public IEnumerable<T> OldItems;
                    public RemovedEventArgs(IEnumerable<T> Old)
                    {
                        this.OldItems = Old;
                    }
                }
                
                public class ReplacedEventArgs<T> : EventArgs
                {
                    public IEnumerable<T> OldItems;
                    public IEnumerable<T> NewItems;
                    public ReplacedEventArgs(IEnumerable<T> Old, IEnumerable<T> New)
                    {
                        this.OldItems = Old;
                        this.NewItems = New;
                    }
                }
                

                注意:我没有在基本方法中手动引发OnCollectionChanged,因为似乎只能使用Reset 操作创建CollectionChangedEventArgs。如果您尝试使用 Reset 提升 OnCollectionChanged 以进行单个项目更改,您的项目控件将出现闪烁,这是您要避免的。

                【讨论】:

                  【解决方案13】:

                  您也可以使用此代码来扩展 ObservableCollection:

                  public static class ObservableCollectionExtend
                  {
                      public static void AddRange<TSource>(this ObservableCollection<TSource> source, IEnumerable<TSource> items)
                      {
                          foreach (var item in items)
                          {
                              source.Add(item);
                          }
                      }
                  }
                  

                  那么你就不需要改变现有代码中的类了。

                  【讨论】:

                  • 这解释了 C# 扩展方法的工作原理,但对问题的重点没有帮助,即如何添加多个项目而不会收到多个更改通知。
                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-04-13
                  • 2015-01-28
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多