Как стать автором
Обновить

Комментарии 17

Обычно делают дополнительные методы типа SuspendNotification и ResumeNotification. Если нотификация была приостановлена в процессе добавления, то после её включения делаем Reset. Если не приостанавливалась — делаем CollectionChanged на каждый элемент.
И непонятно, зачем Вы делаете «collection.ToList()». Вам не нужен список внутри реализации. Зачем лишнюю память занимать?
И непонятно, зачем Вы делаете «collection.ToList()». Вам не нужен список внутри реализации. Зачем лишнюю память занимать?


Чтобы исключить возможный бесконечный цикл ниже:
foreach (var item in items)


Напишите, пожалуйста, в комментах реализацию SuspendNotification и ResumeNotification.
Чтобы исключить возможный бесконечный цикл ниже:

Наверно, я чего-то недопонимаю. Причём тут бесконечный цикл? И как ToList позволит его избежать?

Напишите, пожалуйста, в комментах реализацию SuspendNotification и ResumeNotification.

Ну блин, это же совсем просто. Сделать флаг, который бы устанавливался в Suspend и сбрасывался в Resume. Ну и всю обвязку. Советую скачать триал Telerik-а и глянуть dotPeek-ом реализацию вот этого: docs.telerik.com/devtools/wpf/api/html/t_telerik_windows_data_radobservablecollection_1.htm
Про бесконечный цикл. Значение переданного параметра IEnumerable может быть реализовано криво, так что получится бесконечный цикл. При использовании ToList он тоже будет бесконечным, но до начала изменения нашей коллекции, а не в момент изменения. Если создание нового объекта раздражает, то можно убрать данную строку.

Спасибо за ссылку.
Жесть. У Вас реально такое случалось в практике? Если кто-то вызвал добавление в коллекцию бесконечного числа элементов, то проблема за пределами коллекции.
Если у Вас действительно в вашем ПО такое может случиться, то лучше обезопаситься на уровне интерфейсов — убрать конструктор «ObservableRangeCollection(IEnumerable collection)» и везде поменять IEnumerable на ICollection.

P. S. Не могли бы Вы ещё привести конкретные примеры «некоторые контролы WPF не работают с изменениями коллекции не по одному элементу, а по несколько.»? Я с таким не встречался. Чисто для самообразования.
P. S. Не могли бы Вы ещё привести конкретные примеры «некоторые контролы WPF не работают с изменениями коллекции не по одному элементу, а по несколько.»? Я с таким не встречался. Чисто для самообразования.

Код из System.Windows.Controls.ItemContainerGenerator, который используется для создания элементов в ItemsControl и его наследниках.
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
    if (sender != this.ItemsInternal && args.Action != NotifyCollectionChangedAction.Reset)
        return;
    switch (args.Action)
    {
    case NotifyCollectionChangedAction.Add:
        if (args.NewItems.Count != 1)
            throw new NotSupportedException(System.Windows.SR.Get("RangeActionsNotSupported"));
        this.OnItemAdded(args.NewItems[0], args.NewStartingIndex);
        break;
    case NotifyCollectionChangedAction.Remove:
        if (args.OldItems.Count != 1)
            throw new NotSupportedException(System.Windows.SR.Get("RangeActionsNotSupported"));
        this.OnItemRemoved(args.OldItems[0], args.OldStartingIndex);
        break;
А также,
CollectionView
CompositeCollectionView
ListCollectionView
CollectionViewSource
Указанную статью я знаю. Можно сделать и так, смысла это не меняет. Но там не учтен п.1 из моей статьи
this.CheckReentrancy();
, который все-таки должен быть. Иначе в процессе обработки данных в обработчиках события CollectionChanged, актуальность данных не гарантируется.
То есть возможны (в зависимости от комбинации операций над данными в текущем методе и в обработчиках события CollectionChanged):
— потерянное обновление (lost update)
— «грязное» чтение (dirty read)
— неповторяющееся чтение (non-repeatable read)
— фантомное чтение (fantom read).
А почему бы просто не написать несколько методов для расширения стандартного ObservableCollection?

public static void ForEach<T>(this IEnumerable<T> collection, Action<T> predicate) { foreach (var item in collection) predicate(item); }

public static void AddRange<T>(this IList<T> collection, IEnumerable<T> itemsToAdd) { itemsToAdd.ForEach(collection.Add); }

public static void RemoveRange<T>(this IList<T> collection, IEnumerable<T> itemsToRemove) { itemsToRemove.ForEach(p => collection.Remove(p)); }

public static void ReplaceRange<T>(this IList<T> collection, IEnumerable<T> itemsToReplace) { collection.Clear(); collection.AddRange(itemsToReplace); }
Чтобы избежать множественного вызова событий:
 PropertyChanged
 PropertyChanged
 OnCollectionChanged

На добавление/удаление каждого элемента у вас вызовется по три события.

Например, если коллекция служит источником для отображения на форме, обновление контрола будет производиться на добавление/удаление каждого элемента, а это не хорошо, если элементов много.
А приведенном мной примере вызовутся три события только в конце обработки всех элементов. То есть обновление контрола произойдет один раз.
Вообще-то в WPF всё гораздо хитрее. И контрол не будет обновляться каждый раз. Это только если специально так через Dispatcher реализовывать.
Нет, в WPF всё тупо.
Особенно печально, если коллекция используется в качестве источника данных для отсортированного представления коллекции. На каждый чих представление будет пересортировываться. При этом контролы не перерисовываются немедленно, это да, но зато серьёзно нагружается ItemContainerGenerator. При операциях добавления большого числа элементов в подключенную к ItemsControl'у коллекцию, даже если в нем включена виртуализация, возникает шторм событий. Производительность добавления при этом на порядки ниже чем при одной нотификации Reset.
Как я понял, единственная цель работы — избежать множественных событий PropertyChanged, PropertyChanged и OnCollectionChanged при массовых изменениях коллекции, а выигрыш по синтаксису практически незначительный и роли не играет. Хорошо бы это было написано в начале.
Да — так оно и есть, спасибо за уточнение. Внес правку.
Дополнение:

При использовании коллекции в WPF, для изменения коллекции асинхронно, не в потоке UI, можно реализовать как предлагается по этой ссылке.

Иначе возникнет ошибка {«this type of collectionview does not support changes to its sourcecollection from a thread different from the dispatcher thread.»}
Вообще, это делается так:
    public class ObservableCollectionEx<T> : ObservableCollection<T>
    {
        private int    m_RefreshDeferred;
        private bool   m_Modified;

        /// <summary>
        /// Raises the <see cref="E:CollectionChanged" /> event.
        /// </summary>
        /// <param name="e">The instance containing the event data.</param>
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (m_RefreshDeferred > 0)
            {
                m_Modified = true;
                return;
            }

            base.OnCollectionChanged(e);
        }

        /// <summary>
        /// Отложить посылку уведомлений об изменении состава коллекции.
        /// </summary>
        /// <returns>Дескриптор.</returns>
        public DeferRefreshHelper DeferRefresh()
        {
            return new DeferRefreshHelper(this);
        }

        /// <summary>
        /// Дескриптор отложенных изменений.
        /// </summary>
        public struct DeferRefreshHelper : IDisposable
        {
            private ObservableCollectionEx<T> m_Owner;

            internal DeferRefreshHelper(ObservableCollectionEx<T> owner)
            {
                if (null == owner)
                    throw new ArgumentNullException("owner");
                m_Owner = owner;
                m_Owner.m_RefreshDeferred++;
            }

            /// <summary>
            /// Уменьшить счетчик отложенной посылки обновлений.
            /// </summary>
            public void Dispose()
            {
                if (null == m_Owner)
                    return;

                var temp = m_Owner;
                m_Owner = null;

                if (0 == --temp.m_RefreshDeferred && temp.m_Modified)
                    temp.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                        NotifyCollectionChangedAction.Reset));
            }
        }
    }


С уровнем малость промахнулся.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.