2015-09-30 2 views
0

Мне часто приходится проектировать одну коллекцию в другую. Это было бы очень легко с помощью оператора Select от linq к объектам:проецируйте одну коллекцию в другую и держите их в синхронизации

var targetCollection = sourceCollection.Select(source => new Target 
{ 
    Source = source, 
    //some other stuff here 
} 

Но мне нужно сохранить коллекции в конечном итоге синхронизированными. Когда новые элементы добавляются или удаляются из sourceCollection, изменения должны отражаться в targetCollection. Я должен сделать что-то вроде этого:

void OnSourceCollectionChanged(){ 
    SyncCollections(sourceCollection, targetCollection) 
} 

void SyncCollections(ICollection<Source> sourceCollection, ICollection<Target> targetCollection) 
{ 
    //find items that are no longer present 
    var newItems = sourceCollection.Where(s => !targetCollection.Any(t => t.Source == s)); 
    //find items that were added 
    var oldItems = targetCollection.Where(t => !sourceCollection.Any(s => t.Source == s)); 

    foreach(var oldItem in oldItems) targetCollection.Remove(oldItem); 

    foreach(var source in newItems){ 
     var target = new Target{ Source = source }; 
     targetCollection.Add(target); 
    } 
} 

Я считаю, что уже есть хорошие библиотеки для решения таких сценариев. Вы можете мне порекомендовать?

Я думаю API, где я просто указать проекцию и, возможно, «равенство компаратор» для сравнения исходного и целевого элемента:

var synchronizer = new CollectionSynchronizer<Source, Target>(
    source => new Target 
    { 
     Source = source 
    }); 

synchronizer.Sync(sourceCollection, targetCollection); 

//or specify filter as well: 
synchronizer.Sync(
    sourceCollection.Where(s => s.Created > DatTime.Now.AddMinutes(-5)), 
    targetCollection); 

ответ

0

Вы можете использовать ObservableCollection для этого:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 

public class Program 
{ 
    public static void Main() 
    { 
     var observableList = new ObservableCollection<string>(); 
     var syncList = new List<string>(observableList); 

     observableList.CollectionChanged += (o,e) => { 
      foreach (var item in e.NewItems){ 
       syncList.Add((string)item); 
      } 
     }; 

     observableList.Add("Test"); 
     Console.WriteLine(syncList[0]); 
    } 
} 
+0

Это далеко не то, что мне нужно, @venerik;) – Liero

0

В если ваша исходная коллекция реализует INotifyCollectionChanged и IEnumerable (например ObservableCollection), вы можете сделать коллекцию оберток.

public class ProxyCollection<TProxy, TSource> : IEnumerable<TProxy>, IDisposable{ 
    private readonly Dictionary<TSource, TProxy> _map = new Dictionary<TSource, TProxy>(); 
    private readonly Func<TSource, TProxy> _proxyFactory; 
    private readonly ObservableCollection<TSource> _sourceCollection; 

    public ProxyCollection(ObservableCollection<TSource> sourceCollection, Func<TSource, TProxy> proxyFactory){ 
     _sourceCollection = sourceCollection; 
     _proxyFactory = proxyFactory; 
     AddItems(sourceCollection); 

     _sourceCollection.CollectionChanged += OnSourceCollectionChanged; 
    } 

    public IEnumerator<TProxy> GetEnumerator(){ 
     return _map.Values.GetEnumerator(); 
    } 

    IEnumerator IEnumerable.GetEnumerator(){ 
     return GetEnumerator(); 
    } 

    private void AddItems(IEnumerable<TSource> sourceCollection){ 
     foreach (TSource sourceItem in sourceCollection){ 
      AddProxy(sourceItem); 
     } 
    } 

    private void AddProxy(TSource source){ 
     _map[source] = _proxyFactory(source); 
    } 

    private void OnSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e){ 
     switch (e.Action){ 
      case NotifyCollectionChangedAction.Add: 
       AddItems(e.NewItems.Cast<TSource>()); 
       break; 
      case NotifyCollectionChangedAction.Remove: 
       RemoveItems(e.OldItems.Cast<TSource>()); 
       break; 
      case NotifyCollectionChangedAction.Replace: 
       ReplaceItems(e.OldItems.Cast<TSource>(), e.NewItems.Cast<TSource>()); 
       break; 
      case NotifyCollectionChangedAction.Move: 
       throw new NotImplementedException("Your code here"); 
      case NotifyCollectionChangedAction.Reset: 
       throw new NotImplementedException("Your code here"); 
      default: 
       throw new ArgumentOutOfRangeException(); 
     } 
    } 

    private void ReplaceItems(IEnumerable<TSource> oldItems, IEnumerable<TSource> newItems){ 
     RemoveItems(oldItems); 
     AddItems(newItems); 
    } 

    private void RemoveItems(IEnumerable<TSource> sourceItems){ 
     foreach (TSource sourceItem in sourceItems){ 
      _map.Remove(sourceItem); 
     } 
    } 

    public void Dispose(){ 
     _sourceCollection.CollectionChanged -= OnSourceCollectionChanged; 

     //optionally 
     foreach (IDisposable proxy in _map.Values.OfType<IDisposable>()){ 
      proxy.Dispose(); 
     } 
    } 
} 

Он также может использовать INotifyCollectionChanged. В этом случае вам необходимо поднять CollectionChanged событие с соответствующими параметрами в методе OnSourceCollectionChanged.

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

+0

У меня есть ваша точка. Благодарю. Всего несколько заметок: 1. Сделать ProxyCollection IDisposable слишком хардкорным, потому что все классы, у которых есть одноразовые члены, также должны быть одноразовыми. 99% ProxyCollection будет членом viewmodel или аналогичным. В этом случае модели просмотра также должны быть одноразовыми. 2. Если коллекция источников является наблюдаемой коллекцией, я бы также получил ProxyCollection из ObservableCollection. 3. он хорош для простых сценариев, но для повторного использования он должен быть более гибким. например, как вы будете обрабатывать многопоточность? – Liero

+0

@ Liero, почему хардкор? Почему бы не разобраться в режиме просмотра ?. Да, вы можете получить из «ObservableCollection». Я дал вам просто идею. Во всяком случае, это лучше, чем быть плотным для Созданного поля и проверять его каждые n минут или около того. О многопоточности - это выходит за рамки вашего вопроса. В моем текущем проекте все репозитории домена работают в одном потоке. Вся синхронизация выполняется во внешнем коде. – Artiom

Смежные вопросы