Коллекции синхронизации MVVM

Существует ли стандартный способ синхронизации коллекции объектов Model с коллекцией соответствующих объектов ModelView в C# и WPF? Я ищу какой-то класс, который будет синхронизировать следующие две коллекции, предполагая, что у меня есть только несколько яблок, и я могу хранить их все в памяти.

Другими словами, я хочу убедиться, что если я добавлю Apple в коллекцию Apples, я бы хотел, чтобы AppleModelView был добавлен в коллекцию AppleModelViews. Я мог бы написать свой собственный, прослушивая событие CollectionChanged каждой коллекции. Это похоже на обычный сценарий, когда кто-то умнее меня определил «правильный способ» сделать это.

public class BasketModel
{
    public ObservableCollection<Apple> Apples { get; }
}

public class BasketModelView
{
    public ObservableCollection<AppleModelView> AppleModelViews { get; }
}

person Jake Pearson    schedule 10.08.2009    source источник
comment
Я не совсем понимаю ваш вопрос. Я могу быть немного медленным сегодня, но вам, возможно, придется повторить это.   -  person Shaun Bowe    schedule 11.08.2009
comment
Я добавил еще один абзац выше, надеюсь, это поможет.   -  person Jake Pearson    schedule 11.08.2009
comment
Зачем отдельные коллекции? Apple может быть подмножеством AppleModelView, тогда в зависимости от того, как ваш Apple получил, заполняются только соответствующие части AppleModelView. В общем, я вообще держу свою модель вне WPF и имею только ViewModel. Модель — это сущность в базе данных или что-то еще.   -  person markmnl    schedule 16.02.2011
comment
Вы также можете использовать преобразователь значений для изменения Apple в AppleModelView во время процесса привязки, зависит от того, хотите ли вы повторно использовать AppleModelView в других местах.   -  person MikeT    schedule 02.06.2015


Ответы (11)


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

void OnApplesCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{    
  // Only add/remove items if already populated. 
  if (!IsPopulated)
    return;

  Apple apple;

  switch (e.Action)
  {
    case NotifyCollectionChangedAction.Add:
      apple = e.NewItems[0] as Apple;
      if (apple != null)
        AddViewModel(asset);
      break;
    case NotifyCollectionChangedAction.Remove:
      apple = e.OldItems[0] as Apple;
      if (apple != null)
        RemoveViewModel(apple);
      break;
  }

}

Могут быть некоторые проблемы с производительностью, когда вы добавляете/удаляете много элементов в ListView.

Мы решили эту проблему: расширив ObservableCollection, добавив методы AddRange, RemoveRange, BinaryInsert и добавив события, которые уведомляют других об изменении коллекции. Вместе с расширенным CollectionViewSource, который временно отключает источник при изменении коллекции, он прекрасно работает.

ХТХ,

Деннис

person Dennis    schedule 11.08.2009

Я использую лениво построенные автоматически обновляемые коллекции:

public class BasketModelView
{
    private readonly Lazy<ObservableCollection<AppleModelView>> _appleViews;

    public BasketModelView(BasketModel basket)
    {
        Func<AppleModel, AppleModelView> viewModelCreator = model => new AppleModelView(model);
        Func<ObservableCollection<AppleModelView>> collectionCreator =
            () => new ObservableViewModelCollection<AppleModelView, AppleModel>(basket.Apples, viewModelCreator);

        _appleViews = new Lazy<ObservableCollection<AppleModelView>>(collectionCreator);
    }

    public ObservableCollection<AppleModelView> Apples
    {
        get
        {
            return _appleViews.Value;
        }
    }
}

Используя следующие ObservableViewModelCollection<TViewModel, TModel>:

namespace Client.UI
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Diagnostics.Contracts;
    using System.Linq;

    public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    {
        private readonly ObservableCollection<TModel> _source;
        private readonly Func<TModel, TViewModel> _viewModelFactory;

        public ObservableViewModelCollection(ObservableCollection<TModel> source, Func<TModel, TViewModel> viewModelFactory)
            : base(source.Select(model => viewModelFactory(model)))
        {
            Contract.Requires(source != null);
            Contract.Requires(viewModelFactory != null);

            this._source = source;
            this._viewModelFactory = viewModelFactory;
            this._source.CollectionChanged += OnSourceCollectionChanged;
        }

        protected virtual TViewModel CreateViewModel(TModel model)
        {
            return _viewModelFactory(model);
        }

        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
            case NotifyCollectionChangedAction.Add:
                for (int i = 0; i < e.NewItems.Count; i++)
                {
                    this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                }
                break;

            case NotifyCollectionChangedAction.Move:
                if (e.OldItems.Count == 1)
                {
                    this.Move(e.OldStartingIndex, e.NewStartingIndex);
                }
                else
                {
                    List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList();
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAt(e.OldStartingIndex);

                    for (int i = 0; i < items.Count; i++)
                        this.Insert(e.NewStartingIndex + i, items[i]);
                }
                break;

            case NotifyCollectionChangedAction.Remove:
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);
                break;

            case NotifyCollectionChangedAction.Replace:
                // remove
                for (int i = 0; i < e.OldItems.Count; i++)
                    this.RemoveAt(e.OldStartingIndex);

                // add
                goto case NotifyCollectionChangedAction.Add;

            case NotifyCollectionChangedAction.Reset:
                Clear();
                for (int i = 0; i < e.NewItems.Count; i++)
                    this.Add(CreateViewModel((TModel)e.NewItems[i]));
                break;

            default:
                break;
            }
        }
    }
}
person Sam Harwell    schedule 01.02.2010
comment
Для тех, кто использует Silverlight, вам нужно будет закомментировать регистр NotifyCollectionChangedAction.Move чем-то вроде #if !SILVERLIGHT. - person dFlat; 25.11.2011
comment
в вашем случае NotifyCollectionChangedAction.Move в случае else может быть ошибка, если NewStartingIndex равен › OldStartingIndex, вы можете исправить это, добавив этот код: if(newIndex > e.OldStartingIndex) newIndex-=e.OldItems.Count; между удалением и вставкой. - person João Portela; 18.09.2012
comment
@JoãoPortela - Можете ли вы опубликовать больше ваших реальных изменений? В исходном коде нет переменной newIndex. - person grantnz; 24.01.2013
comment
@grantnz Взгляните на это, это проясняет ситуацию? - person João Portela; 24.01.2013
comment
@JoãoPortela - Да, понял. Спасибо - person grantnz; 24.01.2013
comment
+1 Спасибо за полный пример кода, а также за то, что я обратил внимание на Code Contracts. - person René; 27.05.2013
comment
Очень полезный ответ. Спасибо! Как насчет реализации IDisposable для устранения утечек памяти public void Dispose() { _source.CollectionChanged -= OnSourceCollectionChanged; } - person bobah75; 27.02.2015
comment
Использование Contracts и Lazy должно быть удалено из этого кода. Это вносит ненужную сложность в этот ответ. - person Maxence; 30.12.2016
comment
@JoãoPortela Похоже, вы пытаетесь исправить случай, когда e.NewStartingIndex выходит за пределы диапазона после удаления элементов, но этот сценарий невозможен. Когда элементы перемещаются, e.NewStartingIndex естественным образом ограничивается допустимым индексом (e.NewStartingIndex + e.OldItems.Count всегда будет меньше или равен размеру исходного списка). Ваш код фактически вводит ошибку: например. если e.OldStartingIndex==0, e.NewStartingIndex==1 и e.OldItems.Count==5, тогда ваш код присваивает -4 для newIndex. - person Collin K; 14.08.2018
comment
Кажется, вы правы, «поэтому NewStartingIndex интерпретируется так, как будто элементы уже были удалены». из: blog.stephencleary.com/2009/07/ - person João Portela; 22.08.2018

Ну, во-первых, я не думаю, что есть единственный "правильный способ" сделать это. Это полностью зависит от вашего приложения. Есть более правильные пути и менее правильные пути.

Учитывая это, мне интересно, зачем вам нужно держать эти коллекции «синхронизированными». Какой сценарий вы рассматриваете, что приведет к их рассинхронизации? Если вы посмотрите на пример кода из , вы увидите, что в большинстве случаев модели синхронизируются с моделями представления просто потому, что каждый раз, когда создается модель, создается и модель представления. Нравится:

void CreateNewCustomer()
{
    Customer newCustomer = Customer.CreateNewCustomer();
    CustomerViewModel workspace = new CustomerViewModel(newCustomer, _customerRepository);
    this.Workspaces.Add(workspace);
    this.SetActiveWorkspace(workspace);
}

Мне интересно, что мешает вам создавать AppleModelView каждый раз, когда вы создаете Apple? Мне кажется, это самый простой способ «синхронизировать» эти коллекции, если только я не понял ваш вопрос неправильно.

person Charlie    schedule 10.08.2009
comment
Спасибо за сообщение. Наверное, я слишком усложнял это в своей голове. Я вернусь к работе. - person Jake Pearson; 11.08.2009

Вы также можете найти пример (и пояснения) здесь: http://blog.lexique-du-net.com/index.php?post/2010/03/02/MV-VM-How-to-keep-collections-of-ViewModel-and-Model-in-sync

Надеюсь, это поможет

person Jonathan ANTOINE    schedule 02.03.2010

Хорошо, я влюблен в этот ответ, поэтому мне пришлось поделиться этой абстрактной фабрикой, которую я добавил к ней для поддержки моей инъекции ctor .

using System;
using System.Collections.ObjectModel;

namespace MVVM
{
    public class ObservableVMCollectionFactory<TModel, TViewModel>
        : IVMCollectionFactory<TModel, TViewModel>
        where TModel : class
        where TViewModel : class
    {
        private readonly IVMFactory<TModel, TViewModel> _factory;

        public ObservableVMCollectionFactory( IVMFactory<TModel, TViewModel> factory )
        {
            this._factory = factory.CheckForNull();
        }

        public ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models )
        {
            Func<TModel, TViewModel> viewModelCreator = model => this._factory.CreateVMFrom(model);
            return new ObservableVMCollection<TViewModel, TModel>(models, viewModelCreator);
        }
    }
}

Что строится на этом:

using System.Collections.ObjectModel;

namespace MVVM
{
    public interface IVMCollectionFactory<TModel, TViewModel>
        where TModel : class
        where TViewModel : class
    {
        ObservableCollection<TViewModel> CreateVMCollectionFrom( ObservableCollection<TModel> models );
    }
}

И это:

namespace MVVM
{
    public interface IVMFactory<TModel, TViewModel>
    {
        TViewModel CreateVMFrom( TModel model );
    }
}

А вот нулевая проверка на полноту:

namespace System
{
    public static class Exceptions
    {
        /// <summary>
        /// Checks for null.
        /// </summary>
        /// <param name="thing">The thing.</param>
        /// <param name="message">The message.</param>
        public static T CheckForNull<T>( this T thing, string message )
        {
            if ( thing == null ) throw new NullReferenceException(message);
            return thing;
        }

        /// <summary>
        /// Checks for null.
        /// </summary>
        /// <param name="thing">The thing.</param>
        public static T CheckForNull<T>( this T thing )
        {
            if ( thing == null ) throw new NullReferenceException();
            return thing;
        }
    }
}
person dFlat    schedule 25.11.2011

Хотя решение Сэма Харвелла уже довольно хорошо, оно имеет две проблемы:

  1. Обработчик событий, зарегистрированный здесь this._source.CollectionChanged += OnSourceCollectionChanged, никогда не будет снят с регистрации, т. е. отсутствует this._source.CollectionChanged -= OnSourceCollectionChanged.
  2. Если обработчики событий когда-либо будут присоединены к событиям моделей представлений, сгенерированных viewModelFactory, невозможно узнать, когда эти обработчики событий могут быть снова отсоединены. (Или вообще говоря: вы не можете подготовить сгенерированные модели представлений к «уничтожению».)

Поэтому я предлагаю решение, которое устраняет оба (коротких) недостатка подхода Сэма Харвелла:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Helpers
{
    public class ObservableViewModelCollection<TViewModel, TModel> : ObservableCollection<TViewModel>
    {
        private readonly Func<TModel, TViewModel> _viewModelFactory;
        private readonly Action<TViewModel> _viewModelRemoveHandler;
        private ObservableCollection<TModel> _source;

        public ObservableViewModelCollection(Func<TModel, TViewModel> viewModelFactory, Action<TViewModel> viewModelRemoveHandler = null)
        {
            Contract.Requires(viewModelFactory != null);

            _viewModelFactory = viewModelFactory;
            _viewModelRemoveHandler = viewModelRemoveHandler;
        }

        public ObservableCollection<TModel> Source
        {
            get { return _source; }
            set
            {
                if (_source == value)
                    return;

                this.ClearWithHandling();

                if (_source != null)
                    _source.CollectionChanged -= OnSourceCollectionChanged;

                _source = value;

                if (_source != null)
                {
                    foreach (var model in _source)
                    {
                        this.Add(CreateViewModel(model));
                    }
                    _source.CollectionChanged += OnSourceCollectionChanged;
                }
            }
        }

        private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    for (int i = 0; i < e.NewItems.Count; i++)
                    {
                        this.Insert(e.NewStartingIndex + i, CreateViewModel((TModel)e.NewItems[i]));
                    }
                    break;

                case NotifyCollectionChangedAction.Move:
                    if (e.OldItems.Count == 1)
                    {
                        this.Move(e.OldStartingIndex, e.NewStartingIndex);
                    }
                    else
                    {
                        List<TViewModel> items = this.Skip(e.OldStartingIndex).Take(e.OldItems.Count).ToList();
                        for (int i = 0; i < e.OldItems.Count; i++)
                            this.RemoveAt(e.OldStartingIndex);

                        for (int i = 0; i < items.Count; i++)
                            this.Insert(e.NewStartingIndex + i, items[i]);
                    }
                    break;

                case NotifyCollectionChangedAction.Remove:
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAtWithHandling(e.OldStartingIndex);
                    break;

                case NotifyCollectionChangedAction.Replace:
                    // remove
                    for (int i = 0; i < e.OldItems.Count; i++)
                        this.RemoveAtWithHandling(e.OldStartingIndex);

                    // add
                    goto case NotifyCollectionChangedAction.Add;

                case NotifyCollectionChangedAction.Reset:
                    this.ClearWithHandling();
                    if (e.NewItems == null)
                        break;
                    for (int i = 0; i < e.NewItems.Count; i++)
                        this.Add(CreateViewModel((TModel)e.NewItems[i]));
                    break;

                default:
                    break;
            }
        }

        private void RemoveAtWithHandling(int index)
        {
            _viewModelRemoveHandler?.Invoke(this[index]);
            this.RemoveAt(index);
        }

        private void ClearWithHandling()
        {
            if (_viewModelRemoveHandler != null)
            {
                foreach (var item in this)
                {
                    _viewModelRemoveHandler(item);
                }
            }

            this.Clear();
        }

        private TViewModel CreateViewModel(TModel model)
        {
            return _viewModelFactory(model);
        }
    }
}

Чтобы решить первую из двух проблем, вы можете просто установить для Source значение null, чтобы избавиться от обработчика событий CollectionChanged.

Чтобы решить вторую из двух проблем, вы можете просто добавить viewModelRemoveHandler, который позволяет «подготовить ваш объект к уничтожению», например. удалив все обработчики событий, прикрепленные к нему.

person Hauke P.    schedule 29.02.2016
comment
Я также заметил, что при получении Reset иногда e.NewItems может быть нулевым. Я только что добавил код для обработки этого случая. - person Hauke P.; 20.05.2016
comment
Можете ли вы привести краткий пример того, как вы бы реализовали viewModelRemoveHandler ? - person Mike Christiansen; 31.05.2020
comment
Уф, это было слишком долго, чтобы создать рабочий пример за короткий промежуток времени. Если я правильно помню, я передал функцию в качестве второго аргумента конструктору. Эта функция удалила все события, которые были привязаны к модели представления. Это помогает? - person Hauke P.; 06.06.2020
comment
Да, я так и предполагал, но я надеялся, что у вас есть хороший пример. Извините за четырехлетний некропост! Спасибо, в любом случае! - person Mike Christiansen; 17.06.2020

«Использование MVVM для обеспечения отмены/повторения. Часть 2. Списки моделей представлений» предоставляет класс MirrorCollection<V, D> для достижения синхронизации моделей представлений и коллекций моделей.

Дополнительные ссылки

  1. Исходная ссылка (в настоящее время она недоступна): Уведомить об изменении » Архив блога » Использование MVVM для обеспечения отмены/повторения. Часть 2. Списки моделирования представления.
person Sergey Vyacheslavovich Brunov    schedule 13.11.2010
comment
вот заархивированная версия этой статьи: https://web.archive.org/web/20110803113629/http://blog.notifychanged.com/2009/01/30/viewmodelling-lists/ - person Jan; 22.05.2020
comment
Привет, @Jan! Большое спасибо за обновление! Я только что обновил ответ на основе вашего обновления. - person Sergey Vyacheslavovich Brunov; 22.05.2020

Я написал несколько вспомогательных классов для переноса наблюдаемых коллекций бизнес-объектов в их аналоги модели представления здесь

person Aran Mulholland    schedule 15.02.2010

Мне очень нравится решение 280Z28. Всего одно замечание. Нужно ли делать циклы для каждого NotifyCollectionChangedAction? Я знаю, что в документах для действий указано «один или несколько элементов», но, поскольку сам ObservableCollection не поддерживает добавление или удаление диапазонов, я думаю, этого никогда не произойдет.

person bertvh    schedule 25.08.2010
comment
Совершенно не по теме, но вы известны как Берт Вермейр? - person El Ronnoco; 14.12.2010
comment
Нет, буква «х» не имеет смысла :) - person bertvh; 16.01.2011
comment
ObservableCollection поддерживает несколько элементов, я считаю, что это WPF, который в основном не поддерживает несколько элементов в одном событии изменения. - person jpierson; 16.05.2013
comment
@jpierson: ObservableCollection поддерживает несколько удалений (то есть очистку), но не добавляет. Я признаю, что метод AddRange был бы полезен, но, заставляя его добавлять по одному, они снижают время отклика при мониторинге элементов графического интерфейса. снижение вероятности блокировки из-за того, что кто-то сразу добавил 10000 элементов в список - person MikeT; 02.06.2015

Сброс коллекции до значения по умолчанию или для соответствия целевому значению - это то, что я нажимал довольно часто.

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

public static class Misc
    {
        public static void SyncCollection<TCol,TEnum>(ICollection<TCol> collection,IEnumerable<TEnum> source, Func<TCol,TEnum,bool> comparer, Func<TEnum, TCol> converter )
        {
            var missing = collection.Where(c => !source.Any(s => comparer(c, s))).ToArray();
            var added = source.Where(s => !collection.Any(c => comparer(c, s))).ToArray();

            foreach (var item in missing)
            {
                collection.Remove(item);
            }
            foreach (var item in added)
            {
                collection.Add(converter(item));
            }
        }
        public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source, EqualityComparer<T> comparer)
        {
            var missing = collection.Where(c=>!source.Any(s=>comparer.Equals(c,s))).ToArray();
            var added = source.Where(s => !collection.Any(c => comparer.Equals(c, s))).ToArray();

            foreach (var item in missing)
            {
                collection.Remove(item);
            }
            foreach (var item in added)
            {
                collection.Add(item);
            }
        }
        public static void SyncCollection<T>(ICollection<T> collection, IEnumerable<T> source)
        {
            SyncCollection(collection,source, EqualityComparer<T>.Default);
        }
    }

который покрывает большинство моих потребностей, первый, вероятно, будет наиболее применимым, поскольку вы также конвертируете типы

примечание: при этом синхронизируются только элементы в коллекции, а не значения внутри них

person MikeT    schedule 02.06.2015

Это небольшая вариация на ответ Сэма Харвелла, реализующая IReadOnlyCollection<> и INotifyCollectionChanged вместо прямого наследования от ObservableCollection<>. Это предотвращает изменение коллекции потребителями, что обычно нежелательно в этом сценарии.

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

/// <summary>
/// A collection that mirrors an <see cref="ObservableCollection{T}"/> source collection 
/// with a transform function to create it's own elements.
/// </summary>
/// <typeparam name="TSource">The type of elements in the source collection.</typeparam>
/// <typeparam name="TDest">The type of elements in this collection.</typeparam>
public class MappedObservableCollection<TSource, TDest>
    : IReadOnlyCollection<TDest>, INotifyCollectionChanged
{
    /// <inheritdoc/>
    public int Count => _mappedCollection.Count;

    /// <inheritdoc/>
    public event NotifyCollectionChangedEventHandler CollectionChanged {
        add { _mappedCollection.CollectionChanged += value; }
        remove { _mappedCollection.CollectionChanged -= value; }
    }

    private readonly Func<TSource, TDest> _elementMapper;
    private readonly ObservableCollection<TDest> _mappedCollection;

    /// <summary>
    /// Initializes a new instance of the <see cref="MappedObservableCollection{TSource, TDest}"/> class.
    /// </summary>
    /// <param name="sourceCollection">The source collection whose elements should be mapped into this collection.</param>
    /// <param name="elementMapper">Function to map elements from the source collection to this collection.</param>
    public MappedObservableCollection(ObservableCollection<TSource> sourceCollection, Func<TSource, TDest> elementMapper)
    {
        if (sourceCollection == null) throw new ArgumentNullException(nameof(sourceCollection));
        _mappedCollection = new ObservableCollection<TDest>(sourceCollection.Select(elementMapper));

        _elementMapper = elementMapper ?? throw new ArgumentNullException(nameof(elementMapper));

        // Update the mapped collection whenever the source collection changes
        // NOTE: Use the weak event pattern here to avoid a memory leak
        // See: https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/weak-event-patterns
        CollectionChangedEventManager.AddHandler(sourceCollection, OnSourceCollectionChanged);
    }

    /// <inheritdoc/>
    IEnumerator<TDest> IEnumerable<TDest>.GetEnumerator()
        => _mappedCollection.GetEnumerator();

    /// <inheritdoc/>
    IEnumerator IEnumerable.GetEnumerator()
        => _mappedCollection.GetEnumerator();

    /// <summary>
    /// Mirror a change event in the source collection into the internal mapped collection.
    /// </summary>
    private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action) {
            case NotifyCollectionChangedAction.Add:
                InsertItems(e.NewItems, e.NewStartingIndex);
                break;
            case NotifyCollectionChangedAction.Remove:
                RemoveItems(e.OldItems, e.OldStartingIndex);
                break;
            case NotifyCollectionChangedAction.Replace:
                RemoveItems(e.OldItems, e.OldStartingIndex);
                InsertItems(e.NewItems, e.NewStartingIndex);
                break;
            case NotifyCollectionChangedAction.Reset:
                _mappedCollection.Clear();
                InsertItems(e.NewItems, 0);
                break;
            case NotifyCollectionChangedAction.Move:
                if (e.OldItems.Count == 1) {
                    _mappedCollection.Move(e.OldStartingIndex, e.NewStartingIndex);
                } else {
                    RemoveItems(e.OldItems, e.OldStartingIndex);

                    var movedItems = _mappedCollection.Skip(e.OldStartingIndex).Take(e.OldItems.Count).GetEnumerator();
                    for (int i = 0; i < e.OldItems.Count; i++) {
                        _mappedCollection.Insert(e.NewStartingIndex + i, movedItems.Current);
                        movedItems.MoveNext();
                    }
                }

                break;
        }
    }

    private void InsertItems(IList newItems, int newStartingIndex)
    {
        for (int i = 0; i < newItems.Count; i++)
            _mappedCollection.Insert(newStartingIndex + i, _elementMapper((TSource)newItems[i]));
    }

    private void RemoveItems(IList oldItems, int oldStartingIndex)
    {
        for (int i = 0; i < oldItems.Count; i++)
            _mappedCollection.RemoveAt(oldStartingIndex);
    }
}
person mark.monteiro    schedule 09.06.2020