CollectionChanged и IList of Items — почему трудности

Я изучаю тему, почему ObservableCollection/ListCollectionView/CollectionView вызывает NotSuportedException при вызове CollectionChanged с параметром IList.

//Throws an exception
private void collectionChanged_Removed(IList items)
{
    if (CollectionChanged != null)
        CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items));
}

Я нашел несколько веб-страниц, посвященных этой теме, и они предлагают либо использовать возможность Reset для принудительной полной перерисовки пользовательского интерфейса, либо просто вызывать для каждого элемента CollectionChanged или более творческий способ: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx

Я просто не могу найти ПОЧЕМУ? Для меня не имеет смысла, почему это должно быть так.

Есть ли шанс, что эта отсутствующая функция, с которой мы все сталкиваемся в какой-то момент нашего цикла разработки, поскольку метод Add требует больших накладных расходов, когда вы хотите быстро добавить несколько элементов, будет реализовано в любое время (.Net 5, C# 6...).

Изменить:

В моем конкретном случае я написал свой собственный класс:

public class ObservableList<T> : IList<T>, IList, IEnumerable<T>,
    INotifyCollectionChanged
{
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    //other stuff...
}

И по-прежнему выдает упомянутое NotSupportedException.


person Rand Random    schedule 04.02.2014    source источник
comment
connect.microsoft.com/visualstudio/feedback/details/515465/ : Закрыто, так как не будет исправлено. Также имеет мало голосов.   -  person Onots    schedule 07.02.2014
comment
@Onots Тот факт, что у ObservableCollection нет AddRange, на самом деле не проблема, потому что достаточно просто написать собственный класс, который это делает. Проблема заключается в обработчике, встроенном в CollectionView не примет его. В частности, он отклоняет любые NotifyCollectionChangedEventArgs, где список OldItems или NewItems содержит несколько элементов.   -  person nmclean    schedule 07.02.2014
comment
Не могли бы вы опубликовать трассировку стека?   -  person antiduh    schedule 07.02.2014
comment
@Rand Random - я понял, что путаю ваш ObservableList со встроенным ObservableCollection в своем ответе, поэтому я убрал эту часть. Можете ли вы включить воспроизводимый сценарий, демонстрирующий точную проблему?   -  person drankin2112    schedule 08.02.2014
comment
@Rand Random - Неважно. VirtualBlackFox прояснил суть проблемы.   -  person drankin2112    schedule 09.02.2014
comment
@Rand Random: вы пробовали использовать BulkObservableCollection‹T› Class вместо этого? Этот класс предлагает AddRange без запуска события для каждого элемента. Есть ли разница?   -  person Jens H    schedule 09.02.2014


Ответы (4)


Вдохновленный ответом VirtualBlackFox, я заглянул под капот классов CollectionView в ILSpy. Похоже, что основная причина почему отсутствия поддержки операций Range заключается в том, что внутри CollectionView используется журнал изменений для централизованного управления ожидающими изменениями всех видов и отправки сообщений для каждого элемента.

По самой своей цели CollectionView может хранить тысячи записей, используемых одновременно с несколькими элементами управления пользовательского интерфейса, представляющими его базовые данные. Таким образом, добавление или удаление записей должно выполняться на атомарной основе, чтобы поддерживать целостность элементов управления пользовательского интерфейса, которые получают доступ к информации о представлении. Вы не можете синхронизировать добавочные изменения с несколькими подписчиками пользовательского интерфейса, используя события массовых изменений, без передачи функций группировки, сортировки и фильтрации CollectionView в элементы управления пользовательского интерфейса, которые его используют.

CollectionView также происходит от System.Windows.Threading.Dispatcher, поэтому проблема может быть связана с тем, как он управляет рабочими элементами в своем потоке. Путь вызова включает защищенный метод ProcessCollectionChanged, специально обрабатывающий отдельные изменения в потоке пользовательского интерфейса. Таким образом, диапазоны обновления могут мешать всей модели многопоточности, которую он использует для взаимодействия с элементами пользовательского интерфейса, которые его используют.

Я полностью согласен с тем, что глупо заставлять потребителей CollectionView переходить от IList к NotifyCollectionChangedEventArgs. Он специально отклоняет все, что имеет длину != 1, и жестко кодирует args.NewItems[0] внутри.

person drankin2112    schedule 07.02.2014

Как сказал @nmclean в комментариях, проблема не в коллекции, испускающей CollectionChanged, а на принимающей стороне.

Если вы посмотрите на код ListCollectionView (используя, например, DotPeek или новые версии Visual Studio, вы можете получить доступ к справочному исходному коду) вы заметите, что каждый раз, когда прикрепленная коллекция изменяется, она вызывает метод ValidateCollectionChangedEventArgs, который выдает, когда изменяется более одного элемента.

private void ValidateCollectionChangedEventArgs(NotifyCollectionChangedEventArgs e)
{
  switch (e.Action)
  {
    case NotifyCollectionChangedAction.Add:
      if (e.NewItems.Count == 1)
        break;
      else
        throw new NotSupportedException(System.Windows.SR.Get("RangeActionsNotSupported"));
...

Остальная часть класса и его базовый класс CollectionView уже являются большими зверями (2710 и 2027 строк в исходном коде, опубликованном в справочном исходном коде), поэтому Microsoft, возможно, хотела избежать поддержки сложного случая, когда рекомендуемая ими коллекция не создает так или иначе.

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

Я не нашел никаких предложений, связанных с добавлением поддержки событий диапазона в Microsoft Connect, но вы должны отправить свои собственные, если это важно для вас.

person Julien Roncaglia    schedule 08.02.2014
comment
+1 за объяснение проблемы. Я думал, что это связано с ObservableCollection. Хороший ответ. - person drankin2112; 09.02.2014

Я думаю, это в основном из соображений производительности. Я также был шокирован, когда увидел, что CollectionView также не принимает значение -1 для NewStartingIndex или OldStartingIndex. Таким образом, CollectionView всегда требует сопоставления элементов с их индексами. Однако не требуется, чтобы это сопоставление было точным (что странно с моей точки зрения), допускается, что NewStartingIndex меньше правильного индекса (если только он не равен -1).

Я думаю, что корень проблемы в том, что большая часть Microsoft по-прежнему считает, что список — это единственный способ реализовать коллекцию, что, конечно же, не соответствует действительности. Здесь создатели NotifyCollectionChangedEventArgs подумали об альтернативах (таких как связанные списки или хешированные коллекции), но ребята из пользовательского интерфейса этого не сделали. Или, по крайней мере, они не хотели поддерживать эти коллекции, так как они появляются довольно редко.

person Georg    schedule 10.02.2014

Временное решение бесполезно. Это только скрывает проблемы. Решение может заключаться в создании событий, на которых вы предоставляете наблюдателям весь новый список. Таким образом, Microsoft не придется реализовывать эффективные обработчики диапазона для каждого типа наблюдателя.

person MrFox    schedule 12.02.2014