将一个集合投射到另一个集合中,并使它们保持同步

本文关键字:集合 同步 一个 另一个 | 更新日期: 2023-09-27 18:15:11

我经常需要将一个集合投影到另一个集合中。这将非常容易使用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);

将一个集合投射到另一个集合中,并使它们保持同步

您可以使用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]);
    }
}

如果你的源集合实现了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。在这种情况下,您必须在OnSourceCollectionChanged方法中使用适当的参数引发CollectionChanged事件。

还可以创建一个内部List集合并公开它。在这种情况下,代理集合将具有与源集合相同索引的代理。