在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<>都会更改它。)

链接地址: http://www.djcxy.com/p/52735.html

上一篇: Is it better to call ToList() or ToArray() in LINQ queries?

下一篇: how to uninstall npm modules in node js?