[C#] ObservableCollection AddRange 메서드를 지원하지 않으므로 INotifyCollectionChanging에 대한 것 외에 추가 된 각 항목에 대해 알림을받습니다.

범위를 추가하고 전체 대량으로 업데이트 할 수 있기를 원합니다.

또한 작업이 완료되기 전에 작업을 취소 할 수 있기를 원합니다 (예 : ‘변경됨’외에 컬렉션 변경).


관련 Q
여러 개체를 한 번에 추가하고 알림을받는 .Net 컬렉션은 무엇입니까?



답변

업데이트 및 최적화 된 C # 7 버전을 참조하십시오 . VB.NET 버전을 제거하고 싶지 않으므로 별도의 답변으로 게시했습니다.

업데이트 된 버전으로 이동

지원되지 않는 것 같습니다. FYI는 스스로 구현했습니다. 도움이되기를 바랍니다.

나는 (함께 사용할 때 유용 VB 버전과 지금부터 당신이 후회 할 수 있도록 컬렉션을 변경하기 전에 이벤트를 발생에 업데이트 DataGrid, ListView, 당신은 “당신은 확실히”사용자에게 확인을 보여줄 수 있음을, 그리고 더 많은)를 업데이트 VB를 version은이 메시지의 맨 아래에 있습니다.

화면이 너무 좁아서 코드를 포함 할 수 없다는 점에 사과드립니다. 마음에 들지 않습니다.

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

씨#:

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


답변

우선 , .NET 저장소 의 API 요청투표하고 의견 하십시오 .

여기에 내 최적화 된 버전이다 ObservableRangeCollection(제임스 Montemagno의 최적화 된 버전의 하나 ).

매우 빠르게 수행되며 가능한 경우 기존 요소를 재사용하고 불필요한 이벤트를 피하거나 가능한 경우 하나의 요소로 배치하는 것을 의미합니다. 이 ReplaceRange방법은 필요한 요소를 적절한 인덱스로 대체 / 제거 / 추가하고 가능한 이벤트를 일괄 처리합니다.

Xamarin.Forms UI에서 테스트하여 대규모 컬렉션에 대한 매우 빈번한 업데이트 (초당 5-7 회 업데이트)에 대한 훌륭한 결과를 제공합니다.

참고 : WPF 는 범위 작업을 수행하는 데 익숙하지 않으므로 WPF UI 관련 작업에서 아래에서 from 등 을 사용하는 등의 작업을 NotSupportedException사용하는 경우을 throw합니다 ( UI에 바인딩되지 않은 경우 여전히 사용할 수 있음 ) .
그러나 해결 방법을 사용할 수 있습니다 .ObservableRangeCollectionListBoxObservableRangeCollection<T>
WpfObservableRangeCollection<T>
실제 솔루션은 CollectionView범위 작업을 처리하는 방법을 알고 있는 것을 만드는 것이지만 여전히 구현할 시간이 없었습니다.

RAW 코드-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가 다음과 같이 더 잘 구현된다고 생각합니다.

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

목록 사본을 저장합니다. 또한 미세 최적화를 원한다면 최대 N 개의 항목을 추가하고 추가 된 N 개 이상의 항목을 재설정하면 재설정 할 수 있습니다.


답변

UI를 사용자 지정 컬렉션에주의해서 바인딩해야합니다. Default CollectionView 클래스는 항목에 대한 단일 알림 만 지원합니다.


답변

필요 에 따라 전화 OnPropertyChanged("Count")OnPropertyChanged("Item[]")행동 증명ObservableCollection . 귀찮게하지 않으면 결과가 무엇인지 모르겠습니다!

다음은 일반적인 관찰 가능한 컬렉션에 각 추가에 대해 두 개의 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가 0이됩니다. 컬렉션 변경은 제대로 작동하지만 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);
}


답변

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
    }
}


답변

예, 자신 만의 맞춤 관찰 가능 컬렉션을 추가하면 충분합니다. UI가 현재 사용하고 있는지 여부에 관계없이 적절한 이벤트를 발생시키는 것을 잊지 마십시오.;) NotifyCollectionChangedEventArgs뿐만 아니라 “Item []”속성 (WPF 측 및 바운드 컨트롤에 필요)에 대한 속성 변경 알림을 발생시켜야합니다. 항목 세트가 추가 된 경우 (범위) 정렬 지원 및 기타 작업뿐만 아니라 이러한 작업을 수행했으며 Presentation 및 Code Behind 레이어에 문제가 없었습니다.