C#中那些常用的集合扩展类型
背景
在我们很多的业务代码中我们需要对原有的一些.NET 框架中的一些基础类型进行扩展才能满足我们具体的业务需求,这个时候我们就需要对其进行自定义扩展,今天的这篇文章主要介绍两种.NET中非常常见的扩展类型,第一种是一个带通知的List,第二种就是我们常用的字典类型的扩展,就是字典中的值是一个IList类型,带着具体的代码,我们来分析一下具体的实现并进行总结,从而加深对这些基础知识的理解。
一 实现一个带通知的List<T>
1.1 场景描述
在很多场景下面我们需要在集合发生变化的时候能够通过一个事件对外进行通知,默认的List<T>并没有此类功能,所以对于这一类需求的业务场景下我们需要自己进行相关的扩展,这样才能够符合我们这一需求,这里我来列举一个在项目中经常用到的一个扩展类,在后面我们会对这个进行具体的分析和使用到的C#知识点进行关注。
1.2 代码实现
这里贴出具体的代码实现
using System; using System.Collections.Generic; using System.Linq; namespace XXX.XXX.Core.Utils { public class ItemsChangedEventArgs<T> : EventArgs { public IList<T> RemovedItems { get; private set; } public IList<T> AddedItems { get; private set; } public ItemsChangedEventArgs(IList<T> removedItems, IList<T> addItems) { RemovedItems = removedItems; AddedItems = addItems; } } public delegate void ListItemsChangedEventHandler<T>(object sender, ItemsChangedEventArgs<T> args); public class NotifyList<T> : List<T> { public static NotifyList<T> Empty { get { return new NotifyList<T>(); } } public event ListItemsChangedEventHandler<T> ItemsChanged; protected void OnItemsChanged(IList<T> removedItems, IList<T> addedItems) { ListItemsChangedEventHandler<T> temp = ItemsChanged; temp?.Invoke(this, new ItemsChangedEventArgs<T>(removedItems, addedItems)); } public new void Add(T item) { base.Add(item); OnItemsChanged(Empty, new List<T> { item }); } public new void AddRange(IEnumerable<T> collection) { base.AddRange(collection); OnItemsChanged(Empty, collection.ToList()); } public new void Clear() { T[] array = new T[this.Count]; this.CopyTo(array); base.Clear(); OnItemsChanged(array.ToList(), Empty); } public new bool Remove(T item) { bool ret = base.Remove(item); if (ret) OnItemsChanged(new List<T> { item }, Empty); return ret; } public new int RemoveAll(Predicate<T> match) { IList<T> removedItems = FindAll(match); int count = base.RemoveAll(match); if (removedItems.Count != count) { throw new Exception("[NotifyList][RemoveAll][The number of elements found by the predicate does not match the number of elements removed.]"); } OnItemsChanged(removedItems, Empty); return count; } public new void RemoveAt(int index) { T removedItem = this[index]; base.RemoveAt(index); OnItemsChanged(new List<T> { removedItem }, Empty); } public new void RemoveRange(int index, int count) { IEnumerable<T> range = this.Skip(index + 1).Take(count); base.RemoveRange(index, count); OnItemsChanged(range.ToList(), Empty); } } }
1.3 注意事项
1 基类中Add这些方法都是非虚方法,这里不能使用重载,所以在自己实现的每一个方法中需要使用 new 关键字进行覆盖。
2 在具体使用的时候需要订阅ItemsChanged事件。
二 实现一个完整的ListDictionary
2.1 场景描述
可能开始还不太清楚这个ListDictionary到底要表达什么意思,其实在很多的业务场景下面我们需要扩展一种类型的Dictionary,那就是这个Dictionary的Value是一个IList的类型,针对这种类型我们就有了如下类型的扩展,我们的这个类需要实现
IDictionary<TKey, IList<TValue>>
这个类实现了完整的增加、删除、查找、索引器、获取迭代器的完整需求,几乎完整的覆盖了所有业务的需求,非常适合作为我们的工具类来进行使用。
2.2 代码实现
我们首先来看看我们需要实现的接口,然后再来看整个类的完整实现。
// // 摘要: // Represents a generic collection of key/value pairs. // // 类型参数: // TKey: // The type of keys in the dictionary. // // TValue: // The type of values in the dictionary. [DefaultMember("Item")] public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable { // // 摘要: // Gets or sets the element with the specified key. // // 参数: // key: // The key of the element to get or set. // // 返回结果: // The element with the specified key. // // 异常: // T:System.ArgumentNullException: // key is null. // // T:System.Collections.Generic.KeyNotFoundException: // The property is retrieved and key is not found. // // T:System.NotSupportedException: // The property is set and the System.Collections.Generic.IDictionary`2 is read-only. TValue this[TKey key] { get; set; } // // 摘要: // Gets an System.Collections.Generic.ICollection`1 containing the keys of the System.Collections.Generic.IDictionary`2. // // 返回结果: // An System.Collections.Generic.ICollection`1 containing the keys of the object // that implements System.Collections.Generic.IDictionary`2. ICollection<TKey> Keys { get; } // // 摘要: // Gets an System.Collections.Generic.ICollection`1 containing the values in the // System.Collections.Generic.IDictionary`2. // // 返回结果: // An System.Collections.Generic.ICollection`1 containing the values in the object // that implements System.Collections.Generic.IDictionary`2. ICollection<TValue> Values { get; } // // 摘要: // Adds an element with the provided key and value to the System.Collections.Generic.IDictionary`2. // // 参数: // key: // The object to use as the key of the element to add. // // value: // The object to use as the value of the element to add. // // 异常: // T:System.ArgumentNullException: // key is null. // // T:System.ArgumentException: // An element with the same key already exists in the System.Collections.Generic.IDictionary`2. // // T:System.NotSupportedException: // The System.Collections.Generic.IDictionary`2 is read-only. void Add(TKey key, TValue value); // // 摘要: // Determines whether the System.Collections.Generic.IDictionary`2 contains an element // with the specified key. // // 参数: // key: // The key to locate in the System.Collections.Generic.IDictionary`2. // // 返回结果: // true if the System.Collections.Generic.IDictionary`2 contains an element with // the key; otherwise, false. // // 异常: // T:System.ArgumentNullException: // key is null. bool ContainsKey(TKey key); // // 摘要: // Removes the element with the specified key from the System.Collections.Generic.IDictionary`2. // // 参数: // key: // The key of the element to remove. // // 返回结果: // true if the element is successfully removed; otherwise, false. This method also // returns false if key was not found in the original System.Collections.Generic.IDictionary`2. // // 异常: // T:System.ArgumentNullException: // key is null. // // T:System.NotSupportedException: // The System.Collections.Generic.IDictionary`2 is read-only. bool Remove(TKey key); // // 摘要: // Gets the value associated with the specified key. // // 参数: // key: // The key whose value to get. // // value: // When this method returns, the value associated with the specified key, if the // key is found; otherwise, the default value for the type of the value parameter. // This parameter is passed uninitialized. // // 返回结果: // true if the object that implements System.Collections.Generic.IDictionary`2 contains // an element with the specified key; otherwise, false. // // 异常: // T:System.ArgumentNullException: // key is null. bool TryGetValue(TKey key, out TValue value); }
关于这个定义在System.Collections.Generic接口中的类型这里就不再赘述,这个是.NET 框架中非常常见的一个接口定义,下面我们具体来看看实现类。
/// <summary> /// A dictionary of lists. /// </summary> /// <typeparam name="TKey">The key to use for lists.</typeparam> /// <typeparam name="TValue">The type of the value held by lists.</typeparam> public sealed class ListDictionary<TKey, TValue> : IDictionary<TKey, IList<TValue>> { Dictionary<TKey, IList<TValue>> innerValues = new Dictionary<TKey, IList<TValue>>(); #region Public Methods /// <summary> /// If a list does not already exist, it will be created automatically. /// </summary> /// <param name="key">The key of the list that will hold the value.</param> public void Add(TKey key) { if (key == null) throw new ArgumentNullException(nameof(key)); CreateNewList(key); } /// <summary> /// Adds a value to a list with the given key. If a list does not already exist, /// it will be created automatically. /// </summary> /// <param name="key">The key of the list that will hold the value.</param> /// <param name="value">The value to add to the list under the given key.</param> public void Add(TKey key, TValue value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); if (innerValues.ContainsKey(key)) { innerValues[key].Add(value); } else { List<TValue> values = CreateNewList(key); values.Add(value); } } private List<TValue> CreateNewList(TKey key) { List<TValue> values = new List<TValue>(); innerValues.Add(key, values); return values; } /// <summary> /// Removes all entries in the dictionary. /// </summary> public void Clear() { innerValues.Clear(); } /// <summary> /// Determines whether the dictionary contains the specified value. /// </summary> /// <param name="value">The value to locate.</param> /// <returns>true if the dictionary contains the value in any list; otherwise, false.</returns> public bool ContainsValue(TValue value) { foreach (KeyValuePair<TKey, IList<TValue>> pair in innerValues) { if (pair.Value.Contains(value)) { return true; } } return false; } /// <summary> /// Determines whether the dictionary contains the given key. /// </summary> /// <param name="key">The key to locate.</param> /// <returns>true if the dictionary contains the given key; otherwise, false.</returns> public bool ContainsKey(TKey key) { if (key == null) throw new ArgumentNullException(nameof(key)); return innerValues.ContainsKey(key); } /// <summary> /// Retrieves the all the elements from the list which have a key that matches the condition /// defined by the specified predicate. /// </summary> /// <param name="keyFilter">The filter with the condition to use to filter lists by their key.</param> /// <returns>The elements that have a key that matches the condition defined by the specified predicate.</returns> public IEnumerable<TValue> FindAllValuesByKey(Predicate<TKey> keyFilter) { foreach (KeyValuePair<TKey, IList<TValue>> pair in this) { if (keyFilter(pair.Key)) { foreach (TValue value in pair.Value) { yield return value; } } } } /// <summary> /// Retrieves all the elements that match the condition defined by the specified predicate. /// </summary> /// <param name="valueFilter">The filter with the condition to use to filter values.</param> /// <returns>The elements that match the condition defined by the specified predicate.</returns> public IEnumerable<TValue> FindAllValues(Predicate<TValue> valueFilter) { foreach (KeyValuePair<TKey, IList<TValue>> pair in this) { foreach (TValue value in pair.Value) { if (valueFilter(value)) { yield return value; } } } } /// <summary> /// Removes a list by key. /// </summary> /// <param name="key">The key of the list to remove.</param> /// <returns><see langword="true" /> if the element was removed.</returns> public bool Remove(TKey key) { if (key == null) throw new ArgumentNullException(nameof(key)); return innerValues.Remove(key); } /// <summary> /// Removes a value from the list with the given key. /// </summary> /// <param name="key">The key of the list where the value exists.</param> /// <param name="value">The value to remove.</param> public void RemoveValue(TKey key, TValue value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); if (innerValues.ContainsKey(key)) { List<TValue> innerList = (List<TValue>)innerValues[key]; innerList.RemoveAll(delegate (TValue item) { return value.Equals(item); }); } } /// <summary> /// Removes a value from all lists where it may be found. /// </summary> /// <param name="value">The value to remove.</param> public void RemoveValue(TValue value) { foreach (KeyValuePair<TKey, IList<TValue>> pair in innerValues) { RemoveValue(pair.Key, value); } } #endregion #region Properties /// <summary> /// Gets a shallow copy of all values in all lists. /// </summary> /// <value>List of values.</value> public IList<TValue> Values { get { List<TValue> values = new List<TValue>(); foreach (IEnumerable<TValue> list in innerValues.Values) { values.AddRange(list); } return values; } } /// <summary> /// Gets the list of keys in the dictionary. /// </summary> /// <value>Collection of keys.</value> public ICollection<TKey> Keys { get { return innerValues.Keys; } } /// <summary> /// Gets or sets the list associated with the given key. The /// access always succeeds, eventually returning an empty list. /// </summary> /// <param name="key">The key of the list to access.</param> /// <returns>The list associated with the key.</returns> public IList<TValue> this[TKey key] { get { if (innerValues.ContainsKey(key) == false) { innerValues.Add(key, new List<TValue>()); } return innerValues[key]; } set { innerValues[key] = value; } } /// <summary> /// Gets the number of lists in the dictionary. /// </summary> /// <value>Value indicating the values count.</value> public int Count { get { return innerValues.Count; } } #endregion #region IDictionary<TKey,List<TValue>> Members /// <summary> /// See <see cref="IDictionary{TKey,TValue}.Add"/> for more information. /// </summary> void IDictionary<TKey, IList<TValue>>.Add(TKey key, IList<TValue> value) { if (key == null) throw new ArgumentNullException(nameof(key)); if (value == null) throw new ArgumentNullException(nameof(value)); innerValues.Add(key, value); } /// <summary> /// See <see cref="IDictionary{TKey,TValue}.TryGetValue"/> for more information. /// </summary> bool IDictionary<TKey, IList<TValue>>.TryGetValue(TKey key, out IList<TValue> value) { value = this[key]; return true; } /// <summary> /// See <see cref="IDictionary{TKey,TValue}.Values"/> for more information. /// </summary> ICollection<IList<TValue>> IDictionary<TKey, IList<TValue>>.Values { get { return innerValues.Values; } } #endregion #region ICollection<KeyValuePair<TKey,List<TValue>>> Members /// <summary> /// See <see cref="ICollection{TValue}.Add"/> for more information. /// </summary> void ICollection<KeyValuePair<TKey, IList<TValue>>>.Add(KeyValuePair<TKey, IList<TValue>> item) { ((ICollection<KeyValuePair<TKey, IList<TValue>>>)innerValues).Add(item); } /// <summary> /// See <see cref="ICollection{TValue}.Contains"/> for more information. /// </summary> bool ICollection<KeyValuePair<TKey, IList<TValue>>>.Contains(KeyValuePair<TKey, IList<TValue>> item) { return ((ICollection<KeyValuePair<TKey, IList<TValue>>>)innerValues).Contains(item); } /// <summary> /// See <see cref="ICollection{TValue}.CopyTo"/> for more information. /// </summary> void ICollection<KeyValuePair<TKey, IList<TValue>>>.CopyTo(KeyValuePair<TKey, IList<TValue>>[] array, int arrayIndex) { ((ICollection<KeyValuePair<TKey, IList<TValue>>>)innerValues).CopyTo(array, arrayIndex); } /// <summary> /// See <see cref="ICollection{TValue}.IsReadOnly"/> for more information. /// </summary> bool ICollection<KeyValuePair<TKey, IList<TValue>>>.IsReadOnly { get { return ((ICollection<KeyValuePair<TKey, IList<TValue>>>)innerValues).IsReadOnly; } } /// <summary> /// See <see cref="ICollection{TValue}.Remove"/> for more information. /// </summary> bool ICollection<KeyValuePair<TKey, IList<TValue>>>.Remove(KeyValuePair<TKey, IList<TValue>> item) { return ((ICollection<KeyValuePair<TKey, IList<TValue>>>)innerValues).Remove(item); } #endregion #region IEnumerable<KeyValuePair<TKey,List<TValue>>> Members /// <summary> /// See <see cref="IEnumerable{TValue}.GetEnumerator"/> for more information. /// </summary> IEnumerator<KeyValuePair<TKey, IList<TValue>>> IEnumerable<KeyValuePair<TKey, IList<TValue>>>.GetEnumerator() { return innerValues.GetEnumerator(); } #endregion #region IEnumerable Members /// <summary> /// See <see cref="System.Collections.IEnumerable.GetEnumerator"/> for more information. /// </summary> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return innerValues.GetEnumerator(); } #endregion }
在这个里面着重需要分析的是其内部索引器和获取迭代器的方式,获取索引器通过传入TKey来获取一个IList<TValue>的完整类型,另外这里获取的迭代器是直接获取innerValues内部的迭代器,也就是Dictionary类中默认的GetEnumerator方法,这个需要重点理解。
2.3 注意事项
1 这个里面就一个需要特别注意,这个里面用到了两个Values,一个是我们定义的对外属性,这里需要注意这个注释:Gets a shallow copy of all values in all lists,即对原始对象的浅拷贝,另外是显式实现ICollection<KeyValuePair<TKey, IList<TValue>>>接口的实现,这个在使用的过程中需要特别注意区分。