C#中实现自定义集合应优先继承Collection而非手写IEnumerable或IList,因其已封装索引、枚举、通知等逻辑,仅需重写InsertItem等虚方法即可添加业务约束,兼顾安全、简洁与数据绑定支持。

直接说结论:C# 中实现自定义集合,**不推荐从头手写 IEnumerable<t></t> 或数组封装类**,而应优先继承 Collection<t></t>、List<t></t> 或实现 IList<t></t> —— 它们已处理好索引、枚举、事件通知等底层逻辑,你只需专注业务约束。
为什么别直接实现 IEnumerable<t></t>
很多人一上来就写 GetEnumerator() + IEnumerator<t></t>,结果发现增删改查都要自己兜底,连 foreach 里用 yield return 都没法自动支持 Count 或索引器。更麻烦的是:LINQ 方法(如 Where、First)返回的是新集合,你的自定义行为(比如“只允许添加偶数”)在链式调用后就丢失了。
-
IEnumerable<t></t>是只读契约,不提供修改能力 - 手动实现
IList<t></t>要重写 20+ 成员,极易遗漏InsertItem、SetItem等关键钩子 - WPF/WinForms 数据绑定依赖
INotifyCollectionChanged,裸实现IEnumerable不触发界面更新
用 Collection<t></t> 快速加校验和拦截
Collection<t></t> 是微软提供的可继承基类,它把所有增删改操作都拆成虚方法(InsertItem、RemoveItem 等),你只需重写需要干预的几个即可,其余保持默认行为。
例如,实现一个“只接受正整数”的集合:
public class PositiveIntCollection : Collection<int>
{
protected override void InsertItem(int index, int item)
{
if (item <= 0) throw new ArgumentException("只允许添加正整数");
base.InsertItem(index, item);
}
<pre class="brush:php;toolbar:false;">protected override void SetItem(int index, int item)
{
if (item <= 0) throw new ArgumentException("只允许设置正整数");
base.SetItem(index, item);
}}
- 不需要重写
GetEnumerator—— 继承自基类,天然支持foreach和 LINQ - 所有修改入口都被统一拦截,不会漏掉
Add、Insert、[i] = x等任意方式 - 若需响应变化(如日志、UI 更新),可覆写
ClearItems并触发自定义事件
何时该实现 IList<t></t>?
只有两种情况值得手动实现接口:性能敏感场景(如固定大小缓冲区)或需要完全控制内存布局(如映射到非托管数组)。否则纯属自找麻烦。
典型反例:有人为“跳过 null 元素”实现 IList<string></string>,结果发现 Count 返回总长度而非非空数,this[0] 可能抛异常 —— 因为接口契约要求 Count 和索引器语义严格一致。
-
IList<t></t>要求Count是 O(1),不能每次遍历计数 -
this[i]必须能在 O(1) 返回第 i 个有效元素,不能跳过 null 后动态重排索引 - 若真要过滤语义,用
Where(x => x != null)更安全、更符合直觉
完整可用的轻量级实例(含源码要点)
下面是一个带唯一性约束 + 自动排序的整数集合,仅 30 行,可直接复制进 .NET 6+ 项目运行:
public class SortedUniqueIntCollection : Collection<int>
{
private readonly SortedSet<int> _inner = new();
<pre class="brush:php;toolbar:false;">public override int Count => _inner.Count;
protected override void InsertItem(int index, int item)
{
if (_inner.Add(item)) base.InsertItem(index, item);
else throw new InvalidOperationException($"重复值 {item} 不允许插入");
}
protected override void RemoveItem(int index)
{
_inner.Remove(this[index]);
base.RemoveItem(index);
}
protected override void ClearItems()
{
_inner.Clear();
base.ClearItems();
}}
注意点:_inner 仅用于去重和排序,真实数据仍存在基类的 Items(List<t></t>)中,所以 foreach 遍历顺序由插入顺序决定 —— 如果你要遍历也按排序顺序,就得放弃继承 Collection<t></t>,转而封装 SortedSet<t></t> 并手动实现 IEnumerable<t></t> 和 IReadOnlyList<t></t>。这个权衡点,文档里常被忽略。


















