При Очистке ObservableCollection в e нет Никаких Объектов. OldItems

Я думал, что добавлю несколько конкретных примеров специально для контроллера представления. Многие из объяснений, не только здесь о переполнении стека, действительно хороши, но я лучше работаю с примерами из реального мира (у @drewag было хорошее начало для этого):

  • Если у вас есть закрытие для обрабатывать ответ от сетевых запросов использования weak, потому что они долгоживущие. Контроллер представления может закрыться до завершения запроса, поэтому self больше не указывает на действительный объект при вызове замыкания.
  • Если у вас есть замыкание, которое обрабатывает событие на кнопке. Это может быть unowned, потому что как только контроллер вида уходит, кнопка и любые другие элементы, на которые он может ссылаться из self, одновременно исчезают. Закрывающий блок также исчезнет одновременно.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }
    
89
задан cplotts 22 October 2008 в 01:57
поделиться

6 ответов

Я решил немного иначе, как я хотел для регистрации в одном событии и обработки всех добавлений и удалений в обработчике событий. Я начал с переопределения события изменения коллекции и перенаправления действий сброса на действия удаления со списком элементов. Все пошло не так, поскольку я использовал наблюдаемую коллекцию в качестве источника элементов для представления коллекции и получил сообщение «Действия диапазона не поддерживаются».

Наконец, я создал новое событие под названием CollectionChangedRange, которое действует так, как я ожидал от встроенной версии. act.

Я не могу себе представить, почему это ограничение было разрешено, и надеюсь, что этот пост, по крайней мере, остановит других от того, чтобы зайти в тупик, как я.

/// <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);
        }
    }

}
4
ответ дан 24 November 2019 в 07:20
поделиться

Я просто просматривал код построения диаграмм в наборах инструментов Silverlight и WPF и заметил, что они также решили эту проблему (подобным образом) ... и я подумал, что смогу вперед и опубликуйте свое решение.

По сути, они также создали производную ObservableCollection и переопределили 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);
        }
    }
}
1
ответ дан 24 November 2019 в 07:20
поделиться

Итак, я решил получить

Microsoft приложила ОЧЕНЬ много работы, чтобы всегда удостовериться, что NotifyCollectionChangedEventArgs не имеет данных при вызове сброса. Я предполагаю, что это было решение производительности / памяти. Если вы сбрасываете коллекцию из 100 000 элементов, я m при условии, что они не хотят дублировать все эти элементы.

Но, поскольку в моих коллекциях никогда не бывает более 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);
    }
2
ответ дан 24 November 2019 в 07:20
поделиться

Для сценария присоединения и отсоединения обработчиков событий к элементам коллекции ObservableCollection также существует решение "на стороне клиента". В коде обработки события вы можете проверить, находится ли отправитель в ObservableCollection, используя метод Contains. Плюсы: вы можете работать с любой существующей ObservableCollection. Минусы: метод Contains работает за O(n), где n - количество элементов в ObservableCollection. Так что это решение для небольших ObservableCollections.

Еще одно решение "со стороны клиента" - использовать обработчик событий в середине. Просто регистрируйте все события в обработчике событий в середине. Этот обработчик событий в свою очередь уведомляет реальный обработчик событий через обратный вызов или событие. Если происходит действие Reset, удалите обратный вызов или событие, создайте новый обработчик событий в центре и забудьте о старом. Этот подход также работает для больших ObservableCollections. Я использовал это для события PropertyChanged (см. код ниже).

    /// <summary>
    /// Helper class that allows to "detach" all current Eventhandlers by setting
    /// DelegateHandler to null.
    /// </summary>
    public class PropertyChangedDelegator
    {
        /// <summary>
        /// Callback to the real event handling code.
        /// </summary>
        public PropertyChangedEventHandler DelegateHandler;
        /// <summary>
        /// Eventhandler that is registered by the elements.
        /// </summary>
        /// <param name="sender">the element that has been changed.</param>
        /// <param name="e">the event arguments</param>
        public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
        {
            if (DelegateHandler != null)
            {
                DelegateHandler(sender, e);
            }
            else
            {
                INotifyPropertyChanged s = sender as INotifyPropertyChanged;
                if (s != null)
                    s.PropertyChanged -= PropertyChangedHandler;
            }   
        }
    }
3
ответ дан 24 November 2019 в 07:20
поделиться

Он не претендует на включение старых элементов, потому что Reset не означает, что список был очищен

Это означает, что произошла какая-то драматическая вещь. место, и стоимость работы над добавлением / удалением, скорее всего, превысит стоимость простого повторного сканирования списка с нуля ... так что это то, что вам следует делать.

MSDN предлагает пример повторной сортировки всей коллекции как кандидата на сброс.

Повторюсь. Сброс не означает очистку , это означает Ваши предположения о списке теперь неверны. Относитесь к нему как к совершенно новому списку . Clear является одним из примеров этого, но могут быть и другие.

Некоторые примеры:
У меня был такой список, в котором было много элементов, и он был привязан к WPF ListView для отображения на экране.
Если вы очистите список и вызовете событие .Reset , производительность будет практически мгновенной, но если вы вместо этого вызовете много отдельных событий .Remove , производительность будет ужасной, поскольку WPF удаляет предметы один за другим. Я также использовал .Reset в моем собственном коде, чтобы указать, что список был повторно отсортирован, вместо того, чтобы выполнять тысячи отдельных операций Move . Как и в случае с Clear, при возникновении множества отдельных событий производительность сильно падает.

46
ответ дан 24 November 2019 в 07:20
поделиться

ObservableCollection, а также интерфейс INotifyCollectionChanged явно написаны с учетом конкретного использования: Построение пользовательского интерфейса и его специфические характеристики производительности.

Когда вы хотите получать уведомления об изменениях коллекции, вас обычно интересуют только события Add и Remove.

Я использую следующий интерфейс:

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

Я также написал свою собственную перегрузку Collection, где:

  • ClearItems вызывает Removing
  • InsertItem вызывает Added
  • RemoveItem вызывает Removing
  • SetItem вызывает Removing и Added

Конечно, можно добавить и AddRange.

2
ответ дан 24 November 2019 в 07:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: