构建高性能泛型列表容器需以t[]为底层存储,封装_count、add/insert/remove逻辑,支持惰性扩容、trimexcess、值类型专用优化,并实现ilist/icollection等接口确保生态兼容。

直接用泛型数组(T[])做底层存储,是构建高性能顺序容器最自然的选择,但它本身有硬性限制:数组长度固定、无法动态扩容、不提供插入/删除逻辑。真正的“自定义泛型列表容器”,不是简单包装一个数组,而是围绕它封装行为、补足能力,并规避其短板。
用泛型数组打底,但绝不裸用
泛型数组 T[] 是零开销的内存布局,缓存友好,访问 O(1),但它只是“内存块”,不是“容器”。你需要在它之上加一层控制逻辑:
- 维护独立的
_count字段记录当前有效元素数,而非依赖array.Length - 封装
Add方法:检查容量,不足时调用Array.Resize或新建更大数组并复制——注意避免频繁小步扩容(推荐 1.5× 或 2× 增长) - 实现
Insert和RemoveAt:手动移动后续元素,明确区分“逻辑删除”(仅减 _count)和“物理收缩”(另起新数组) - 暴露只读属性
Count和索引器this[int i],内部做越界检查,不对外暴露原始数组引用
预分配 + 惰性扩容,把性能损耗摊薄
很多自定义容器一上来就 new T[4],结果刚 Add 5 次就 Resize。这不是优化,是浪费。更务实的做法是:
- 构造函数支持传入初始容量(
int capacity = 0),capacity 为 0 时延迟分配,首次 Add 再初始化为 4 或 8 - 扩容时不盲目翻倍,对已知规模场景(如日志缓冲区固定 1024 条),允许构造时锁定容量并禁用扩容
- 提供
TrimExcess()方法,供调用方在确认不再添加后主动收缩,释放冗余内存
值类型专用路径,彻底绕过装箱陷阱
如果你的容器明确只用于值类型(如 Vector3、int、Guid),可以加 where T : struct 约束。这带来两个实际好处:
- 编译器确保不会传入引用类型,避免意外 null 引用或装箱语义混淆
- 可安全使用
Span<t></t>和Memory<t></t>进行切片、拷贝,比如AsSpan().CopyTo(anotherArray),全程无 GC 压力 - 配合
Unsafe.AsRef或指针操作(在unsafe上下文中)可进一步压榨访问速度,适用于高频数值计算场景
接口兼容性不能丢,否则失去泛型价值
再快的自定义容器,如果不能被现有生态识别,就等于废掉一半价值。务必实现关键泛型接口:
-
IList<t></t>:让 foreach、LINQ ToList()、第三方序列化器能无缝对接 -
ICollection<t></t>:支持Count、Contains、CopyTo等通用操作 - 可选
IReadOnlyList<t></t>:为只读使用场景提供轻量视图,避免误改 - 别忘了
GetEnumerator()返回IEnumerator<t></t>,这是 foreach 的基础










