这是 Sam Harwell
答案 的轻微变化,实现了
IReadOnlyCollection<>
和
INotifyCollectionChanged
,而不是直接继承自
ObservableCollection<>
。 这样可以防止使用者修改集合,在这种情况下通常是不必要的。
此实现还使用
CollectionChangedEventManager
将事件处理程序附加到源集合,以避免在源集合没有同时释放时出现内存泄漏的情况。
public class MappedObservableCollection<TSource, TDest>
: IReadOnlyCollection<TDest>, INotifyCollectionChanged
{
public int Count => _mappedCollection.Count;
public event NotifyCollectionChangedEventHandler CollectionChanged {
add { _mappedCollection.CollectionChanged += value; }
remove { _mappedCollection.CollectionChanged -= value; }
}
private readonly Func<TSource, TDest> _elementMapper;
private readonly ObservableCollection<TDest> _mappedCollection;
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));
CollectionChangedEventManager.AddHandler(sourceCollection, OnSourceCollectionChanged);
}
IEnumerator<TDest> IEnumerable<TDest>.GetEnumerator()
=> _mappedCollection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> _mappedCollection.GetEnumerator();
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);
}
}