3 回答
TA贡献1776条经验 获得超12个赞
除非您只需要一个数组来满足其他约束,否则您应该使用它ToList
。在大多数情况下ToArray
会分配更多的内存ToList
。
两者都使用数组进行存储,但ToList
具有更灵活的约束。它需要数组至少与集合中元素的数量一样大。如果阵列较大,那不是问题。但是,ToArray
需要将数组的大小精确地调整为元素的数量。
为了满足这种约束,ToArray
通常会进行一次分配ToList
。一旦它有一个足够大的数组,它就会分配一个完全正确大小的数组,并将元素复制回该数组。唯一能避免这种情况的是当数组的增长算法恰好与需要存储的元素数量一致时(绝对是少数)。
编辑
有几个人问我在List<T>
数值中有多余的未使用内存的后果。
这是一个有效的问题。如果创建的集合是长期存在的,在创建后永远不会被修改并且很有可能在Gen2堆中着陆,那么您可能最好先采取额外的分配ToArray
。
总的来说,虽然我发现这是罕见的情况。更常见的是看到很多ToArray
调用立即传递给其他短暂的内存使用,在这种情况下ToList
显然更好。
这里的关键是剖析,剖析,然后再详细介绍一些。
TA贡献1993条经验 获得超5个赞
性能差异将是微不足道的,因为它List<T>
是作为动态大小的阵列实现的。调用ToArray()
(使用内部Buffer<T>
类来增长数组)或ToList()
(调用List<T>(IEnumerable<T>)
构造函数)将最终成为将它们放入数组并增长数组直到它适合所有数组的问题。
如果您希望具体确认这一事实,请查看Reflector中相关方法的实现 - 您将看到它们归结为几乎完全相同的代码。
TA贡献1871条经验 获得超13个赞
其他(好)答案集中在将发生的微观性能差异上。
此信息仅仅是一个补充提语义差别之间存在IEnumerator<T>(产生由阵列T[])相比于由一个返回List<T>。
通过示例最佳说明:
IList<int> source = Enumerable.Range(1, 10).ToArray(); // try changing to .ToList()
foreach (var x in source)
{
if (x == 5)
source[8] *= 100;
Console.WriteLine(x);
}
上面的代码将运行,没有异常并产生输出:
1
2
3
4
五
6
7
8
900
10
这表明IEnumarator<int>由an返回的数据int[]不会跟踪自创建枚举器以来数组是否已被修改。
请注意,我将局部变量声明source为IList<int>。通过这种方式,我确保C#编译器不会将foreach语句优化为等同于for (var idx = 0; idx < source.Length; idx++) { /* ... */ }循环的东西。如果我使用C#编译器可能会这样做var source = ...;。在我当前版本的.NET框架中,这里使用的实际枚举器是非公共引用类型,System.SZArrayHelper+SZGenericArrayEnumerator`1[System.Int32]但当然这是一个实现细节。
现在,如果我改变.ToArray()成.ToList(),我只得到:
1
2
3
4
五
随后是一个System.InvalidOperationException爆炸性的说法:
收集被修改; 枚举操作可能无法执行。
在这种情况下,底层枚举器是public mutable value-type System.Collections.Generic.List`1+Enumerator[System.Int32](IEnumerator<int>在这种情况下,盒子装在盒子里,因为我使用IList<int>)。
总之,由枚举器生成的枚举器List<T>会跟踪列表在枚举期间是否发生更改,而生成的枚举T[]器则不会。因此,在.ToList()和之间选择时要考虑这种差异.ToArray()。
人们经常添加一个额外的 .ToArray()或.ToList()绕过一个集合来跟踪它是否在枚举器的生命周期中被修改。
(如果有人想知道如何在List<>跟踪上收集是否被修改,有一个私人领域_version在这个类,这是每次改变的List<>更新。)
- 3 回答
- 0 关注
- 1227 浏览
添加回答
举报