将一个集合投射到另一个集合中,并使它们保持同步
本文关键字:集合 同步 一个 另一个 | 更新日期: 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集合并公开它。在这种情况下,代理集合将具有与源集合相同索引的代理。