当前位置:  开发笔记 > 编程语言 > 正文

ObservableCollection不支持AddRange方法,因此除了INotifyCollectionChanging外,我还会收到添加的每个项目的通知?

如何解决《ObservableCollection不支持AddRange方法,因此除了INotifyCollectionChanging外,我还会收到添加的每个项目的通知?》经验,为你挑选了5个好方法。

我希望能够添加一个范围并获得整个批量的更新.

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


相关Q 哪个.Net集合可以一次添加多个对象并收到通知?



1> Shimmy..:

请参阅更新和优化的C#7版本.我不想删除VB.NET版本所以我只是在一个单独的答案中发布它.

转到更新版本

似乎它不受支持,我自己实施,仅供参考,希望它有所帮助:

我更新了VB版本,从现在起它在更改集合之前引发了一个事件,因此你可以后悔(在使用时很有用DataGrid,ListView还有更多,你可以向用户显示"你确定"的确认),更新后的VB版本位于此消息的底部.

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

VB.NET:

Imports System.Collections.Specialized

Namespace System.Collections.ObjectModel
    ''' 
    ''' Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed.
    ''' 
    ''' 
    Public Class ObservableRangeCollection(Of T) : Inherits System.Collections.ObjectModel.ObservableCollection(Of T)

        ''' 
        ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
        ''' 
        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

        ''' 
        ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
        ''' 
        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

        ''' 
        ''' Clears the current collection and replaces it with the specified item.
        ''' 
        Public Sub Replace(ByVal item As T)
            ReplaceRange(New T() {item})
        End Sub
        ''' 
        ''' Clears the current collection and replaces it with the specified collection.
        ''' 
        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

        ''' 
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
        ''' 
        ''' 
        Public Sub New()
            MyBase.New()
        End Sub
        ''' 
        ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
        ''' 
        ''' collection: The collection from which the elements are copied.
        ''' The collection parameter cannot be null.
        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;

///  
/// Represents a dynamic data collection that provides notifications when items get added, removed, or when the whole list is refreshed. 
///  
///  
public class ObservableRangeCollection : ObservableCollection
{
    ///  
    /// Adds the elements of the specified collection to the end of the ObservableCollection(Of T). 
    ///  
    public void AddRange(IEnumerable collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    ///  
    /// Removes the first occurence of each item in the specified collection from ObservableCollection(Of T). 
    ///  
    public void RemoveRange(IEnumerable collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        foreach (var i in collection) Items.Remove(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    ///  
    /// Clears the current collection and replaces it with the specified item. 
    ///  
    public void Replace(T item)
    {
        ReplaceRange(new T[] { item });
    }

    ///  
    /// Clears the current collection and replaces it with the specified collection. 
    ///  
    public void ReplaceRange(IEnumerable collection)
    {
        if (collection == null) throw new ArgumentNullException("collection");

        Items.Clear();
        foreach (var i in collection) Items.Add(i);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    ///  
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class. 
    ///  
    public ObservableRangeCollection()
        : base() { }

    ///  
    /// Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection. 
    ///  
    /// collection: The collection from which the elements are copied. 
    /// The collection parameter cannot be null. 
    public ObservableRangeCollection(IEnumerable 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)
    ''' 
    ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class.
    ''' 
    ''' 
    Public Sub New()
        MyBase.New()
    End Sub

    ''' 
    ''' Initializes a new instance of the System.Collections.ObjectModel.ObservableCollection(Of T) class that contains elements copied from the specified collection.
    ''' 
    ''' collection: The collection from which the elements are copied.
    ''' The collection parameter cannot be null.
    Public Sub New(ByVal collection As IEnumerable(Of T))
        MyBase.New(collection)
    End Sub

    ''' 
    ''' Adds the elements of the specified collection to the end of the ObservableCollection(Of T).
    ''' 
    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


    ''' 
    ''' Inserts the collection at specified index.
    ''' 
    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


    ''' 
    ''' Removes the first occurence of each item in the specified collection from ObservableCollection(Of T).
    ''' 
    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



    ''' 
    ''' Clears the current collection and replaces it with the specified item.
    ''' 
    Public Sub Replace(ByVal item As T)
        ReplaceRange(New T() {item})
    End Sub

    ''' 
    ''' Clears the current collection and replaces it with the specified collection.
    ''' 
    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


@HiTechMagic Sc​​ott不正确.因为这会在Collection 的受保护项IList 上调用Add,所以没有发送这样的已更改事件,我已经通过单元测试验证了使用AddRange计算对CollectionChanged事件的调用,我可以确认只有一个.
需要调用`OnPropertyChanged("Count");`和`OnPropertyChanged("Item []");`在add/remove/replace范围方法中完全模仿标准的ObservableCollection.
C#代码不起作用:附加信息:不支持范围操作.
斯科特多尔曼是对的.调用内置的Add方法将触发每次添加3个事件(项目计数的NotifyPropertyChange,项目索引[]的NotifyPropertyChange和添加的项目的NotifyCollectionChanged事件).要获得大型更新的最佳性能,需要完全替换ObservableCollection.
当您在迭代集合并添加/删除项目时,这是否仍然会引发单个集合更改事件?
C#代码坏了,是的,但它很容易修复.只需更改`NotifyCollectionChangedAction.Reset`的所有`NotifyCollectionChangedAction.*`实例,并且不传递集合.我的猜测是让它重新评估整个集合,但基本上同样好(并且仍然比在每次添加时触发多个通知要好得多).仅供参考:这给我带来了大约需要2分钟左右的时间,非常感谢!

2> Shimmy..:

首先,请对.NET repo 上的API请求进行投票和评论.

这是我的优化版本ObservableRangeCollection(James Montemagno的优化版本之一).

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

在Xamarin.Forms UI上进行测试,可以非常频繁地更新大型集合(每秒5-7次更新).

Note: Since WPF is not accustomed to work with range operations, it will throw a NotSupportedException, when using the ObservableRangeCollection from below in WPF UI-related work, such as binding it to a ListBox etc. (you can still use the ObservableRangeCollection if not bound to UI).
However you can use the WpfObservableRangeCollection workaround.
The real solution would be creating a CollectionView that knows how to deal with range operations, but I still didn't have the time to implement this.

RAW Code - open as Raw, then do Ctrl+A to select all, then Ctrl+C to copy.

// 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
{
  /// 
  /// Implementation of a dynamic data collection based on generic Collection<T>,
  /// implementing INotifyCollectionChanged to notify listeners
  /// when items get added, removed or the whole list is refreshed.
  /// 
  public class ObservableRangeCollection : ObservableCollection
  {
    //------------------------------------------------------
    //
    //  Private Fields
    //
    //------------------------------------------------------

    #region Private Fields    
    [NonSerialized]
    private DeferredEventsCollection _deferredEvents;
    #endregion Private Fields


    //------------------------------------------------------
    //
    //  Constructors
    //
    //------------------------------------------------------

    #region Constructors
    /// 
    /// Initializes a new instance of ObservableCollection that is empty and has default initial capacity.
    /// 
    public ObservableRangeCollection() { }

    /// 
    /// 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.
    /// 
    /// The collection whose elements are copied to the new list.
    /// 
    /// The elements are copied onto the ObservableCollection in the
    /// same order they are read by the enumerator of the collection.
    /// 
    ///  collection is a null reference 
    public ObservableRangeCollection(IEnumerable collection) : base(collection) { }

    /// 
    /// Initializes a new instance of the ObservableCollection class
    /// that contains elements copied from the specified list
    /// 
    /// The list whose elements are copied to the new list.
    /// 
    /// The elements are copied onto the ObservableCollection in the
    /// same order they are read by the enumerator of the list.
    /// 
    ///  list is a null reference 
    public ObservableRangeCollection(List list) : base(list) { }

    #endregion Constructors

    //------------------------------------------------------
    //
    //  Public Methods
    //
    //------------------------------------------------------

    #region Public Methods

    /// 
    /// Adds the elements of the specified collection to the end of the .
    /// 
    /// 
    /// The collection whose elements should be added to the end of the .
    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.
    /// 
    ///  is null.
    public void AddRange(IEnumerable collection)
    {
      InsertRange(Count, collection);
    }

    /// 
    /// Inserts the elements of a collection into the  at the specified index.
    /// 
    /// The zero-based index at which the new elements should be inserted.
    /// The collection whose elements should be inserted into the List.
    /// The collection itself cannot be null, but it can contain elements that are null, if type T is a reference type.                
    ///  is null.
    ///  is not in the collection range.
    public void InsertRange(int index, IEnumerable 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 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)Items;
      target.InsertRange(index, collection);

      OnEssentialPropertiesChanged();

      if (!(collection is IList list))
        list = new List(collection);

      OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, list, index));
    }


    ///  
    /// Removes the first occurence of each item in the specified collection from the .
    /// 
    /// The items to remove.        
    ///  is null.
    public void RemoveRange(IEnumerable collection)
    {
      if (collection == null)
        throw new ArgumentNullException(nameof(collection));

      if (Count == 0)
      {
        return;
      }
      else if (collection is ICollection countable)
      {
        if (countable.Count == 0)
          return;
        else if (countable.Count == 1)
          using (IEnumerator enumerator = countable.GetEnumerator())
          {
            enumerator.MoveNext();
            Remove(enumerator.Current);
            return;
          }
      }
      else if (!(ContainsAny(collection)))
      {
        return;
      }

      CheckReentrancy();

      var clusters = new Dictionary>();
      var lastIndex = -1;
      List 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 { item };
        }
      }

      OnEssentialPropertiesChanged();

      if (Count == 0)
        OnCollectionReset();
      else
        foreach (KeyValuePair> cluster in clusters)
          OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, cluster.Value, cluster.Key));

    }

    /// 
    /// Iterates over the collection and removes all items that satisfy the specified match.
    /// 
    /// The complexity is O(n).
    /// 
    /// Returns the number of elements that where 
    ///  is null.
    public int RemoveAll(Predicate match)
    {
      return RemoveAll(0, Count, match);
    }

    /// 
    /// Iterates over the specified range within the collection and removes all items that satisfy the specified match.
    /// 
    /// The complexity is O(n).
    /// The index of where to start performing the search.
    /// The number of items to iterate on.
    /// 
    /// Returns the number of elements that where 
    ///  is out of range.
    ///  is out of range.
    ///  is null.
    public int RemoveAll(int index, int count, Predicate 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 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 { 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;
    }

    /// 
    /// Removes a range of elements from the >.
    /// 
    /// The zero-based starting index of the range of elements to remove.
    /// The number of elements to remove.
    /// The specified range is exceeding the collection.
    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, see constructors
      var items = (List)Items;
      List removedItems = items.GetRange(index, count);

      CheckReentrancy();

      items.RemoveRange(index, count);

      OnEssentialPropertiesChanged();

      if (Count == 0)
        OnCollectionReset();
      else
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, index));
    }

    ///  
    /// Clears the current collection and replaces it with the specified collection,
    /// using the default .
    ///              
    /// The items to fill the collection with, after clearing it.
    ///  is null.
    public void ReplaceRange(IEnumerable collection)
    {
      ReplaceRange(0, Count, collection, EqualityComparer.Default);
    }

    /// 
    /// Clears the current collection and replaces it with the specified collection,
    /// using the specified comparer to skip equal items.
    /// 
    /// The items to fill the collection with, after clearing it.
    /// An  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.
    ///  is null.
    ///  is null.
    public void ReplaceRange(IEnumerable collection, IEqualityComparer comparer)
    {
      ReplaceRange(0, Count, collection, comparer);
    }

    /// 
    /// Removes the specified range and inserts the specified collection,
    /// ignoring equal items (using ).
    /// 
    /// The index of where to start the replacement.
    /// The number of items to be replaced.
    /// The collection to insert in that location.
    ///  is out of range.
    ///  is out of range.
    ///  is null.
    public void ReplaceRange(int index, int count, IEnumerable collection)
    {
      ReplaceRange(index, count, collection, EqualityComparer.Default);
    }

    /// 
    /// Removes the specified range and inserts the specified collection in its position, leaving equal items in equal positions intact.
    /// 
    /// The index of where to start the replacement.
    /// The number of items to be replaced.
    /// The collection to insert in that location.
    /// The comparer to use when checking for equal items.
    ///  is out of range.
    ///  is out of range.
    ///  is null.
    ///  is null.
    public void ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer 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 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 list))
        list = new List(collection);

      using (BlockReentrancy())
      using (DeferEvents())
      {
        var rangeCount = index + count;
        var addedCount = list.Count;

        var changesMade = false;
        List
            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 { @new };
              oldCluster = new List { old };
            }
            else
            {
              newCluster.Add(@new);
              oldCluster.Add(old);
            }

            changesMade = true;
          }
        }

        OnRangeReplaced(i, newCluster, oldCluster);

        //exceeding position
        if (count != addedCount)
        {
          var items = (List)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

    /// 
    /// Called by base class Collection<T> when the list is being cleared;
    /// raises a CollectionChanged event to any listeners.
    /// 
    protected override void ClearItems()
    {
      if (Count == 0)
        return;

      CheckReentrancy();
      base.ClearItems();
      OnEssentialPropertiesChanged();
      OnCollectionReset();
    }

    /// 
    /// Called by base class Collection<T> when an item is set in list;
    /// raises a CollectionChanged event to any listeners.
    /// 
    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);
    }

    /// 
    /// Raise CollectionChanged event to any listeners.
    /// Properties/methods modifying this ObservableCollection will raise
    /// a collection changed event through this virtual method.
    /// 
    /// 
    /// When overriding this method, either call its base implementation
    /// or call  to guard against reentrant collection changes.
    /// 
    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

    /// 
    /// Helper function to determine if a collection contains any elements.
    /// 
    /// The collection to evaluate.
    /// 
    private static bool ContainsAny(IEnumerable collection)
    {
      using (IEnumerator enumerator = collection.GetEnumerator())
        return enumerator.MoveNext();
    }

    /// 
    /// Helper to raise Count property and the Indexer property.
    /// 
    private void OnEssentialPropertiesChanged()
    {
      OnPropertyChanged(EventArgsCache.CountPropertyChanged);
      OnIndexerPropertyChanged();
    }

    /// 
    /// /// Helper to raise a PropertyChanged event for the Indexer property
    /// /// 
    private void OnIndexerPropertyChanged() =>
      OnPropertyChanged(EventArgsCache.IndexerPropertyChanged);

    /// 
    /// Helper to raise CollectionChanged event to any listeners
    /// 
    private void OnCollectionChanged(NotifyCollectionChangedAction action, object oldItem, object newItem, int index) =>
      OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, newItem, oldItem, index));

    /// 
    /// Helper to raise CollectionChanged event with action == Reset to any listeners
    /// 
    private void OnCollectionReset() =>
      OnCollectionChanged(EventArgsCache.ResetCollectionChanged);

    /// 
    /// Helper to raise event for clustered action and clear cluster.
    /// 
    /// The index of the item following the replacement block.
    /// 
    /// 
    //TODO should have really been a local method inside ReplaceRange(int index, int count, IEnumerable collection, IEqualityComparer comparer),
    //move when supported language version updated.
    private void OnRangeReplaced(int followingItemIndex, ICollection newCluster, ICollection oldCluster)
    {
      if (oldCluster == null || oldCluster.Count == 0)
      {
        Debug.Assert(newCluster == null || newCluster.Count == 0);
        return;
      }

      OnCollectionChanged(
          new NotifyCollectionChangedEventArgs(
              NotifyCollectionChangedAction.Replace,
              new List(newCluster),
              new List(oldCluster),
              followingItemIndex - oldCluster.Count));

      oldCluster.Clear();
      newCluster.Clear();
    }

    #endregion Private Methods

    //------------------------------------------------------
    //
    //  Private Types
    //
    //------------------------------------------------------

    #region Private Types
    private sealed class DeferredEventsCollection : List, IDisposable
    {
      private readonly ObservableRangeCollection _collection;
      public DeferredEventsCollection(ObservableRangeCollection 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

  }

  /// 
  /// To be kept outside , since otherwise, a new instance will be created for each generic type used.
  /// 
  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);
  }
}


[C#7模式匹配](https://docs.microsoft.com/zh-cn/dotnet/csharp/pattern-matching#the-is-type-pattern-expression)配合。

3> Sam Saffron..:

我认为AddRange更好地实现如下:

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

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


http://blogs.msdn.com/nathannesbit/archive/2009/04/20/addrange-and-observablecollection.aspx

4> Dominic Hopt..:

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



5> weston..:

证明需要OnPropertyChanged("Count")OnPropertyChanged("Item[]")呼叫以便按照表现行事ObservableCollection.请注意,如果您不打扰,我不知道后果是什么!

这是一个测试方法,显示普通可观察集合中每个添加有两个PropertyChange事件.一个为"Count"一个为一个"Item[]".

[TestMethod]
public void TestAddSinglesInOldObsevableCollection()
{
  int colChangedEvents = 0;
  int propChangedEvents = 0;
  var collection = new ObservableCollection();
  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();
  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 :(
}


这里的FYI是来自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);
}

推荐阅读
凹凸曼00威威_694
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有