在LINQ查询中调用ToList()或ToArray()会更好吗?
我经常遇到这样的情况:我想在我声明的位置评估一个查询。 这通常是因为我需要多次迭代它,计算起来很昂贵。 例如:
string raw = "...";
var lines = (from l in raw.Split('n')
let ll = l.Trim()
where !string.IsNullOrEmpty(ll)
select ll).ToList();
这工作正常。 但如果我不打算修改结果,那么我不如调用ToArray()
而不是ToList()
。
然而,我想知道ToArray()是否是通过首先调用ToList()来实现的,因此与调用ToList()相比内存效率更低。
我疯了吗? 我应该只是调用ToArray()
- 在知道内存不会被分配两次的情况下安全可靠吗?
除非你只需要一个数组来满足其他限制,否则你应该使用ToList
。 在大多数情况下, ToArray
将分配比ToList
更多的内存。
两者都使用数组进行存储,但ToList
具有更灵活的约束。 它需要数组至少与集合中元素的数量一样大。 如果数组较大,那不是问题。 但是, ToArray
需要数组的大小与元素的数量完全一致。
为了满足这个约束, ToArray
经常比ToList
做更多的分配。 一旦它有足够大的数组,它将分配一个完全正确大小的数组,并将这些元素复制回该数组中。 唯一可以避免的情况是,数组的增长算法恰好与需要存储的元素数量(绝对少数)一致。
编辑
有几个人问我关于在List<T>
值中有多余的未使用内存的结果。
这是一个值得关注的问题。 如果创建的集合是长期存在的,创建后永远不会修改,并且有很高的登陆Gen2堆的机会,那么您最好先在ToArray
的额外分配上ToArray
准备。
一般来说,虽然我认为这是罕见的情况。 看到很多ToArray
调用更常见,这些调用会立即传递给其他短暂内存使用,在这种情况下, ToList
显然更好。
这里的关键是进行配置文件,配置文件,然后再配置文件。
性能差异将是微不足道的,因为List<T>
被实现为动态大小的数组。 调用ToArray()
(它使用一个内部的Buffer<T>
类来增长数组)或ToList()
(它调用List<T>(IEnumerable<T>)
构造函数)将最终成为将它们放入一个数组,并增长数组,直到它适合所有。
如果您希望具体确认这一事实,请查看Reflector中有关方法的实现 - 您将看到它们归结为几乎相同的代码。
(七年后......)
其他几个(好的)答案都集中在将发生的微观性能差异上。
这篇文章仅仅是提到数组( T[]
)产生的IEnumerator<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 5 6 7 8 900 10
这表明由int[]
返回的IEnumarator<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 5
后面跟着一个System.InvalidOperationException
爆炸说:
收藏已修改; 枚举操作可能不会执行。
在这种情况下,底层枚举器是公共可变值类型System.Collections.Generic.List`1+Enumerator[System.Int32]
(在这种情况下,因为使用IList<int>
所以在IEnumerator<int>
框内装箱)。
总之,由List<T>
生成的枚举器会跟踪枚举期间列表是否更改,而T[]
生成的枚举器不会。 因此,在.ToList()
和.ToArray()
之间进行选择时请考虑这种差异。
人们经常添加一个额外的.ToArray()
或.ToList()
来绕开一个集合,该集合跟踪它是否在枚举器的生命周期中被修改。
(如果有人想知道List<>
如何跟踪集合是否被修改,那么这个类中有一个私有字段_version
,每次更新List<>
都会更改它。)
上一篇: Is it better to call ToList() or ToArray() in LINQ queries?