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

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










