为何List<T>采用自有缓存空数组而非Array.Empty<T>?
关于List自定义空数组缓存的技术疑问解答
先看对应的List
#pragma warning disable CA1825, IDE0300 // avoid the extra generic instantiation for Array.Empty<T>() private static readonly T[] s_emptyArray = new T[0]; #pragma warning restore CA1825, IDE0300
该缓存空数组会在List<T>.ToArray()(列表为空时)等场景中被使用。
下面针对疑问逐一解答:
1. 为何要自定义缓存空数组?为何Array.Empty被视为需要避免的“额外泛型实例化”?
Array.Empty<T>()的实现依赖独立的泛型静态类EmptyArray<T>,内部持有空数组实例。调用Array.Empty<T>()时,CLR需要先实例化EmptyArray<T>这个泛型类;而List<T>本身已是泛型类,自身持有s_emptyArray可以避免额外的泛型类实例化开销——无需再加载、初始化EmptyArray<T>这个额外泛型类型。
2. 若其他地方使用了Array.Empty,运行时会存在两个缓存空实例,此时s_emptyArray反而成了额外实例化?
这种情况确实存在,但对于List<T>这类高频使用的核心集合,自有缓存的收益远大于“多一个实例”的成本:
List<T>的使用量极大,绝大多数场景下,List<T>的空数组访问频次远高于直接调用Array.Empty<T>()的情况,自有缓存能减少大量EmptyArray<T>的实例化触发。- 空数组本身内存开销极小(仅数组元数据,无元素存储),多一个实例的内存成本几乎可以忽略。
3. 是为了规避Array.Empty委托给EmptyArray的性能影响?这种泛型访问的性能影响是否足以证明合理性?
是的,核心目的就是减少泛型类型加载与访问的开销。Array.Empty<T>()需要跨泛型类访问静态字段,而List<T>的s_emptyArray是自身类的静态字段,访问路径更短:
- 直接访问自身类静态字段,无需额外的类型解析、加载步骤,在高频调用场景(比如大量空List转数组)中,性能差异会被放大。
- 官方团队通过性能测试验证过该优化的收益,对于
List<T>这类基础核心库组件,哪怕是微秒级的性能提升,在大规模应用中都会累积成显著的优化效果。
4. 在高性能场景下,我们是否也应考虑创建并使用自有缓存空数组?
分场景判断:
- 如果你的代码是高频调用的泛型类/方法,且空数组是常用路径,那么自定义缓存空数组是值得的,能避免额外的泛型类型实例化和跨类访问开销。
- 如果是普通业务代码,或空数组使用频率很低,完全没必要——
Array.Empty<T>()已经足够高效,额外维护自有缓存会增加代码复杂度,收益远小于成本。
内容的提问来源于stack exchange,提问作者user764754
相关产品推荐
相关产品推荐

