programing

관찰 가능한 컬렉션을 지울 때 항목이 없습니다.이전 항목

stoneblock 2023. 4. 29. 08:15

관찰 가능한 컬렉션을 지울 때 항목이 없습니다.이전 항목

저는 여기에 정말로 저를 당황하게 하는 무언가가 있습니다.

저는 아이템들로 채워진 관찰 가능한 T 컬렉션을 가지고 있습니다.CollectionChanged 이벤트에 이벤트 핸들러도 첨부되어 있습니다.

컬렉션을 지우면 e가 포함된 CollectionChanged 이벤트가 발생합니다.NotifyCollectionChangedAction으로 설정된 작업입니다.리셋. 좋아요, 정상입니다.하지만 이상한 것은 그것도 마찬가지입니다.오래된 아이템이나.새 항목에는 아무 것도 들어 있지 않습니다.나는 예상합니다.이전 항목은 컬렉션에서 제거된 모든 항목으로 채워집니다.

이거 본 사람 있어요?만약 그렇다면, 그들은 어떻게 그것을 피했을까요?

일부 배경:CollectionChanged 이벤트를 사용하여 다른 이벤트를 추가하거나 분리하고 있으며, 이로 인해 e에 항목이 없을 수 있습니다.이전 항목...저는 그 행사에서 벗어날 수 없을 것입니다.


명확화: 문서에 이러한 방식으로 행동해야 한다고 명시되어 있지 않다는 것을 알고 있습니다.하지만 다른 모든 행동에 대해, 그것은 자신이 한 일을 저에게 알려줍니다.그래서, 제 추측은 그것이 제게...Clear/Reset의 경우에도 마찬가지입니다.


당신이 직접 재현하고자 하는 경우 아래 샘플 코드입니다.먼저 시작합니다.

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

다음은 코드 뒤에 있습니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace ObservableCollection
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            _integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
        }

        private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
                    break;
                case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        private void addButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Add(25);
        }

        private void moveButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Move(0, 19);
        }

        private void removeButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.RemoveAt(0);
        }

        private void replaceButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection[0] = 50;
        }

        private void resetButton_Click(object sender, RoutedEventArgs e)
        {
            _integerObservableCollection.Clear();
        }

        private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
    }
}

재설정이 목록을 지운 것을 의미하지 않기 때문에 이전 항목을 포함한다고 주장하지 않습니다.

이는 어떤 극적인 일이 일어났다는 것을 의미하며, 추가/제거 비용은 목록을 처음부터 다시 스캔하는 비용을 초과할 가능성이 높습니다.그래서 그것이 당신이 해야 할 일입니다.

MSDN은 전체 컬렉션을 재설정 후보로 다시 정렬하는 예를 제안합니다.

반복합니다.재설정은 명확한 것이 아니라 목록에 대한 가정이 이제 유효하지 않다는 의미합니다. 완전히 새로운 목록인 것처럼 처리합니다.분명한 것은 이것의 한 예이지만, 다른 예도 있을 수 있습니다.

들어 다음과 같습니다.
가 이렇게 목록을, 항들이어있는목이목가같록있지다,었고은 WPF ▁a▁to▁i▁like▁w▁this▁datab다▁list▁withpf▁been.ListView화면에 표시합니다.
목록을 지우고 다음을 높이면.Reset이벤트, 공연은 꽤 즉각적이지만, 만약 당신이 많은 개인을 키운다면..Remove이벤트, WPF가 항목을 하나씩 제거하기 때문에 성능이 형편없습니다.저도 사용해봤어요..Reset수천 명의 개인을 발행하는 대신 목록이 다시 작성되었음을 나타내는 나만의 코드로.Moveㅠㅠ 로 많은 개별 때 큰 .Clear와 마찬가지로 많은 개별 이벤트를 발생시킬 때 성능이 크게 저하됩니다.

여기서도 같은 문제가 있었습니다.변경된 컬렉션의 재설정 작업에는 이전 항목이 포함되지 않습니다.해결 방법이 있었습니다. 대신 다음 확장 방법을 대신 사용했습니다.

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

Clear() 기능을 지원하지 않고 NotSupported(지원되지 않음)를 던졌습니다.재설정 작업에 대해 변경된 수집 이벤트에서 예외가 발생했습니다.RemoveAll은 적절한 OldItems와 함께 CollectionChanged 이벤트에서 Remove 액션을 트리거합니다.

좋아요, 저는 이것이 매우 오래된 질문이라는 것을 알지만, 저는 그 문제에 대한 좋은 해결책을 생각해냈고 제가 공유할 것이라고 생각했습니다.이 솔루션은 많은 훌륭한 답변에서 영감을 얻었지만 다음과 같은 이점이 있습니다.

  • 새 클래스를 만들고 관찰 가능한 컬렉션에서 메서드를 재정의할 필요가 없습니다.
  • NotifyCollectionChanged의 작동을 방해하지 않음(Reset을 방해하지 않음)
  • 반사를 사용하지 않습니다.

코드는 다음과 같습니다.

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

이 확장 방법은 단순히 다음을 수행합니다.Action컬렉션을 지우기 전에 호출됩니다.

다른 옵션은 Reset 이벤트를 다음과 같이 OldItems 속성에 모든 항목이 지워진 하나의 Remove 이벤트로 바꾸는 것입니다.

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

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

장점:

  1. 승인된 답변에 따라 추가 이벤트를 구독할 필요가 없습니다.

  2. 제거된 각 개체에 대해 이벤트를 생성하지 않습니다(다른 제안된 솔루션에 따라 여러 개의 제거된 이벤트가 발생함).

  3. 가입자는 필요에 따라 이벤트 핸들러를 추가/제거하기 위해 모든 이벤트에 대해 새 항목 및 이전 항목만 확인하면 됩니다.

단점:

  1. 재설정 이벤트 없음

  2. 목록 복사본을 만드는 데 드는 오버헤드가 작습니다.

  3. ???

EDIT 2012-02-23

안타깝게도 WPF 목록 기반 컨트롤에 바인딩된 경우 여러 요소를 사용하여 관찰 가능한 컬렉션 NoReset 컬렉션을 지우면 "Range actions not supported" 예외가 발생합니다.이 제한이 있는 컨트롤과 함께 사용하기 위해 ObservableCollectionNoReset 클래스를 다음과 같이 변경했습니다.

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

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

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

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

    // Additional constructors omitted.
 }

이는 RangeActionsSupported가 false(기본값)인 경우만큼 효율적이지 않습니다. 컬렉션의 개체당 제거 알림이 하나씩 생성되기 때문입니다.

좋아요, 아직도 관찰 가능한 컬렉션이 제가 원하는 대로 작동하기를 바라지만...아래 코드는 제가 하게 된 것입니다.기본적으로 저는 True Observable Collection이라는 T 컬렉션을 새로 만들고 ClearItems 메서드를 재정의하여 ClearClear 이벤트를 발생시켰습니다.

이 True Observable Collection을 사용하는 코드에서 이 Clearing 이벤트를 사용하여 해당 시점에 컬렉션에 남아 있는 항목을 반복하여 분리하려는 이벤트에 대해 분리를 수행합니다.

이 접근 방식이 다른 사람에게도 도움이 되기를 바랍니다.

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

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

사용자가 한 번에 많은 항목을 추가하거나 제거하는 효율성을 활용하면서 하나의 이벤트만 실행할 수 있는 동시에 UI 요소의 요구 사항을 충족할 수 있는 솔루션을 찾았습니다.다른 모든 사용자가 요소 목록을 추가 및 제거할 때 이벤트 변수를 재설정합니다.

이 솔루션에는 CollectionChanged 이벤트를 재정의하는 작업이 포함됩니다.이 이벤트를 시작할 때 등록된 각 핸들러의 대상을 실제로 보고 유형을 결정할 수 있습니다.에만 IcollectionView가 NotifyCollectionChangedAction.Reset두 개 이상의 항목이 변경될 때 해당 항목을 선택하여 제거되거나 추가된 항목의 전체 목록이 포함된 적절한 이벤트 변수를 다른 모든 사용자에게 제공할 수 있습니다.다음은 구현입니다.

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

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

    public BaseObservableCollection() : base(){}
    public BaseObservableCollection(IEnumerable<T> data) : base(data){}

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

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

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

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

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

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

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

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

저는 한 이벤트에 등록하고 이벤트 핸들러에서 모든 추가 및 제거를 처리하고 싶었기 때문에 이 문제를 약간 다른 방식으로 해결했습니다.수집 변경 이벤트를 재정의하고 재설정 작업을 항목 목록이 있는 제거 작업으로 리디렉션하기 시작했습니다.관찰 가능한 컬렉션을 컬렉션 보기의 항목 소스로 사용하다가 "Range actions not supported"(범위 작업이 지원되지 않음) 메시지가 표시되어 이 모든 것이 잘못되었습니다.

마침내 CollectionChangedRange라는 새 이벤트를 만들었습니다. 이 이벤트는 내장된 버전이 작동할 것으로 예상되는 방식으로 작동합니다.

저는 왜 이 제한이 허용되는지 상상할 수 없고 이 게시물이 적어도 제가 했던 막다른 길로 가는 다른 사람들을 막기를 바랍니다.

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

    [field: NonSerialized]
    public event NotifyCollectionChangedEventHandler CollectionChangedRange;

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

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

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

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

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

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

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

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

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

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

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

}

이것이 관찰 가능한 컬렉션의 작동 방식입니다. 이 문제를 해결하려면 사용자 자신의 목록을 관찰 가능한 컬렉션 외부에 보관(액션이 추가될 때 목록에 추가, 액션이 제거될 때 제거됨 등)한 다음 목록을 관찰 가능한 컬렉션과 비교하여 액션이 재설정될 때 제거된 모든 항목(또는 추가된 항목)을 가져올 수 있습니다.

또 다른 옵션은 IList 및 INNotifyCollectionChanged를 구현하는 고유한 클래스를 만든 다음 해당 클래스 내에서 이벤트를 추가하거나 분리할 수 있습니다(또는 원하는 경우 이전 항목을 Clear로 설정). 이것은 정말 어렵지는 않지만 많은 타이핑입니다.

이벤트 핸들러를 관찰 가능한 컬렉션의 요소에 연결 및 분리하는 시나리오에는 "클라이언트 측" 솔루션도 있습니다.이벤트 처리 코드에서는 Contains 메서드를 사용하여 보낸 사람이 Observable Collection에 있는지 확인할 수 있습니다.Pro: 기존의 모든 관측 가능한 컬렉션으로 작업할 수 있습니다.단점: 포함 메소드는 O(n)와 함께 실행됩니다. 여기서 n은 관측 가능한 컬렉션의 요소 수입니다.이것은 소규모 관측 가능 컬렉션을 위한 솔루션입니다.

또 다른 "클라이언트 측" 솔루션은 중간에 이벤트 핸들러를 사용하는 것입니다.중간에 있는 이벤트 핸들러에 모든 이벤트를 등록하기만 하면 됩니다.이 이벤트 핸들러는 콜백 또는 이벤트를 통해 실제 이벤트 핸들러에 알립니다.재설정 작업이 발생하면 콜백 또는 이벤트를 제거하고 중간에 새 이벤트 핸들러를 만들고 이전 이벤트 핸들러는 잊어버립니다.이 접근 방식은 큰 관측 가능한 컬렉션에도 적용됩니다.PropertyChanged 이벤트에 사용했습니다(아래 코드 참조).

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

NotifyCollectionChangedEventArgs를 보면 Replace, Remove 또는 Move 작업의 결과로 변경된 항목만 OldItems에 포함되어 있는 것 같습니다.Clear에 포함된 내용은 없습니다.지우기는 이벤트를 실행하지만 제거된 항목을 등록하지 않고 제거 코드를 전혀 호출하지 않는 것 같습니다.

음, 저는 그것으로 더러워지기로 결심했습니다.

Microsoft는 재설정을 호출할 때 항상 NotifyCollectionChangedEventArgs에 데이터가 없는지 확인하기 위해 많은 노력을 기울였습니다.성능/메모리에 대한 결정이라고 생각합니다.100,000개의 요소로 컬렉션을 재설정하는 경우 해당 요소를 모두 복제하지 않을 것으로 예상됩니다.

하지만 제 컬렉션은 100개 이상의 요소를 가지고 있지 않기 때문에, 저는 그것에 문제가 없다고 생각합니다.

어쨌든 다음 방법으로 상속된 클래스를 만들었습니다.

protected override void ClearItems()
{
    CheckReentrancy();
    List<TItem> oldItems = new List<TItem>(Items);

    Items.Clear();

    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));

    NotifyCollectionChangedEventArgs e =
        new NotifyCollectionChangedEventArgs
        (
            NotifyCollectionChangedAction.Reset
        );

        FieldInfo field =
            e.GetType().GetField
            (
                "_oldItems",
                BindingFlags.Instance | BindingFlags.NonPublic
            );
        field.SetValue(e, oldItems);

        OnCollectionChanged(e);
    }

관찰 가능한 컬렉션과 INOTIFY CollectionChanged 인터페이스는 특정 용도, 즉 UI 빌드 및 특정 성능 특성을 염두에 두고 명확하게 작성되었습니다.

수집 변경 알림을 원하는 경우 일반적으로 이벤트 추가 및 제거에만 관심이 있습니다.

다음 인터페이스를 사용합니다.

using System;
using System.Collections.Generic;

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

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

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

저는 또한 컬렉션의 오버로드를 다음과 같이 작성했습니다.

  • 항목 상승 지우기 제거
  • 항목 상승 삽입 추가
  • 항목 상승 제거 제거
  • SetItem 상승 제거 및 추가

물론 AddRange도 추가할 수 있습니다.

Silverlight 및 WPF 툴킷의 차트 작성 코드 중 일부를 검토하던 중 이 문제도 비슷한 방식으로 해결되었다는 것을 알게 되었습니다. 이에 따라 솔루션을 게시해 보겠습니다.

기본적으로 파생된 Observable Collection을 생성하고 ClearItems를 재정의하여 지울 각 항목에 대해 Remove(제거)를 호출했습니다.

코드는 다음과 같습니다.

/// <summary>
/// An observable collection that cannot be reset.  When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event 
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
    public NoResetObservableCollection()
    {
    }

    /// <summary>
    /// Clears all items in the collection by removing them individually.
    /// </summary>
    protected override void ClearItems()
    {
        IList<T> items = new List<T>(this);
        foreach (T item in items)
        {
            Remove(item);
        }
    }
}

이것은 중요한 주제입니다. 왜냐하면 제 생각에는 마이크로소프트가 제대로 일을 하지 못했기 때문입니다.다시 한 번저를 오해하지 마세요, 저는 마이크로소프트를 좋아하지만, 마이크로소프트는 완벽하지 않습니다!

저는 이전 댓글들을 거의 다 읽었습니다.Microsoft가 Clear()를 제대로 프로그래밍하지 않았다고 생각하는 모든 사람들의 의견에 동의합니다.

제 생각에는, 적어도 어떤 사건에서 물체를 분리하는 것을 가능하게 하기 위해서는 논쟁이 필요합니다... 하지만 저는 또한 그 사건의 영향을 이해합니다.그리고 나서, 저는 이 제안된 해결책을 생각해냈습니다.

저는 그것이 모두를 행복하게 하거나 적어도 대부분의 사람들을 행복하게 하기를 바랍니다.

에릭.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;

namespace WpfUtil.Collections
{
    public static class ObservableCollectionExtension
    {
        public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl)
        {
            foreach (T item in obsColl)
            {
                while (obsColl.Count > 0)
                {
                    obsColl.RemoveAt(0);
                }
            }
        }

        public static void RemoveAll<T>(this ObservableCollection<T> obsColl)
        {
            if (obsColl.Count > 0)
            {
                List<T> removedItems = new List<T>(obsColl);
                obsColl.Clear();

                NotifyCollectionChangedEventArgs e =
                    new NotifyCollectionChangedEventArgs
                    (
                        NotifyCollectionChangedAction.Remove,
                        removedItems
                    );
                var eventInfo =
                    obsColl.GetType().GetField
                    (
                        "CollectionChanged",
                        BindingFlags.Instance | BindingFlags.NonPublic
                    );
                if (eventInfo != null)
                {
                    var eventMember = eventInfo.GetValue(obsColl);
                    // note: if eventMember is null
                    // nobody registered to the event, you can't call it.
                    if (eventMember != null)
                        eventMember.GetType().GetMethod("Invoke").
                            Invoke(eventMember, new object[] { obsColl, e });
                }
            }
        }
    }
}

단순하게 하려면 ClearItem 메서드를 재정의하고 원하는 작업을 수행하십시오. 즉, 이벤트에서 항목을 분리합니다.

public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>,    {
{
  protected override void ClearItems()
  {
    Do what ever you want
    base.ClearItems();
  }

  rest of the code omitted
}

단순하고 깨끗하며 수집 코드 내에 포함됩니다.

저도 같은 문제가 있었고, 이것이 제 해결책이었습니다.효과가 있는 것 같습니다.이 접근 방식에 잠재적인 문제가 있다고 생각하는 사람이 있습니까?

// overriden so that we can call GetInvocationList
public override event NotifyCollectionChangedEventHandler CollectionChanged;

protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
    if (collectionChanged != null)
    {
        lock (collectionChanged)
        {
            foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
            {
                try
                {
                    handler(this, e);
                }
                catch (NotSupportedException ex)
                {
                    // this will occur if this collection is used as an ItemsControl.ItemsSource
                    if (ex.Message == "Range actions are not supported.")
                    {
                        handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                    }
                    else
                    {
                        throw ex;
                    }
                }
            }
        }
    }
}

다음은 제 수업에서 유용한 몇 가지 방법입니다.

public void SetItems(IEnumerable<T> newItems)
{
    Items.Clear();
    foreach (T newItem in newItems)
    {
        Items.Add(newItem);
    }
    NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}

public void AddRange(IEnumerable<T> newItems)
{
    int index = Count;
    foreach (T item in newItems)
    {
        Items.Add(item);
    }
    NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index);
    NotifyCollectionChanged(e);
}

public void RemoveRange(int startingIndex, int count)
{
    IList<T> oldItems = new List<T>();
    for (int i = 0; i < count; i++)
    {
        oldItems.Add(Items[startingIndex]);
        Items.RemoveAt(startingIndex);
    }
    NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex);
    NotifyCollectionChanged(e);
}

// this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don't support
new public void Clear()
{
    RemoveRange(0, Count);
}

public void RemoveWhere(Func<T, bool> criterion)
{
    List<T> removedItems = null;
    int startingIndex = default(int);
    int contiguousCount = default(int);
    for (int i = 0; i < Count; i++)
    {
        T item = Items[i];
        if (criterion(item))
        {
            if (removedItems == null)
            {
                removedItems = new List<T>();
                startingIndex = i;
                contiguousCount = 0;
            }
            Items.RemoveAt(i);
            removedItems.Add(item);
            contiguousCount++;
        }
        else if (removedItems != null)
        {
            NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
            removedItems = null;
            i = startingIndex;
        }
    }
    if (removedItems != null)
    {
        NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
    }
}

private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    OnPropertyChanged(new PropertyChangedEventArgs("Count"));
    OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
    OnCollectionChanged(e);
}

ObservableCollection에서 파생된 또 다른 "단순한" 솔루션을 찾았지만 Reflection을 사용하기 때문에 그다지 우아하지 않습니다...마음에 드신다면 여기가 제 해결책입니다.

public class ObservableCollectionClearable<T> : ObservableCollection<T>
{
    private T[] ClearingItems = null;

    protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
                if (this.ClearingItems != null)
                {
                    ReplaceOldItems(e, this.ClearingItems);
                    this.ClearingItems = null;
                }
                break;
        }
        base.OnCollectionChanged(e);
    }

    protected override void ClearItems()
    {
        this.ClearingItems = this.ToArray();
        base.ClearItems();
    }

    private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems)
    {
        Type t = e.GetType();
        System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        if (foldItems != null)
        {
            foldItems.SetValue(e, olditems);
        }
    }
}

여기서 ClearItems 메서드의 배열 필드에 현재 요소를 저장한 다음 OnCollectionChanged 호출을 가로채고 기본을 시작하기 전에 e._oldItems 개인 필드를 덮어씁니다.컬렉션 변경 시

ClearItems 메서드를 재정의하고 Remove 작업 및 OldItems를 사용하여 이벤트를 발생시킬 수 있습니다.

public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
{
    protected override void ClearItems()
    {
        CheckReentrancy();
        var items = Items.ToList();
        base.ClearItems();
        OnPropertyChanged(new PropertyChangedEventArgs("Count"));
        OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1));
    }
}

의 일부System.Collections.ObjectModel.ObservableCollection<T>실현:

public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
    protected override void ClearItems()
    {
        CheckReentrancy();
        base.ClearItems();
        OnPropertyChanged(CountString);
        OnPropertyChanged(IndexerName);
        OnCollectionReset();
    }

    private void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    private void OnCollectionReset()
    {
        OnCollectionChanged(new   NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }

    private const string CountString = "Count";

    private const string IndexerName = "Item[]";
}

http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx

이 설명서를 눈을 뜨고 뇌를 켠 상태에서 읽으십시오.마이크로소프트는 모든 것을 제대로 했습니다.컬렉션이 재설정 알림을 보낼 때 컬렉션을 다시 검색해야 합니다.각 항목에 대해 추가/제거(제거 및 수집에 다시 추가)를 던지는 비용이 너무 많이 들기 때문에 재설정 알림이 표시됩니다.

오리온 에드워즈의 말이 전적으로 옳습니다.설명서를 읽을 때 좀 더 넓게 생각해 보세요.

만약 당신이ObservableCollection명확하지 않습니다. 그러면 아래 코드를 사용해 보십시오.도움이 될 수 있습니다.

private TestEntities context; // This is your context

context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context

언급URL : https://stackoverflow.com/questions/224155/when-clearing-an-observablecollection-there-are-no-items-in-e-olditems