为了账号安全,请及时绑定邮箱和手机立即绑定

在LINQ查询中调用ToList()或ToArray()会更好吗?

在LINQ查询中调用ToList()或ToArray()会更好吗?

泛舟湖上清波郎朗 2019-08-06 14:38:36
在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()- 安全且安全地知道内存不会被分配两次吗?
查看完整描述

3 回答

?
叮当猫咪

TA贡献1776条经验 获得超12个赞

除非您只需要一个数组来满足其他约束,否则您应该使用它ToList。在大多数情况下ToArray会分配更多的内存ToList

两者都使用数组进行存储,但ToList具有更灵活的约束。它需要数组至少与集合中元素的数量一样大。如果阵列较大,那不是问题。但是,ToArray需要将数组的大小精确地调整为元素的数量。

为了满足这种约束,ToArray通常会进行一次分配ToList。一旦它有一个足够大的数组,它就会分配一个完全正确大小的数组,并将元素复制回该数组。唯一能避免这种情况的是当数组的增长算法恰好与需要存储的元素数量一致时(绝对是少数)。

编辑

有几个人问我在List<T>数值中有多余的未使用内存的后果。

这是一个有效的问题。如果创建的集合是长期存在的,在创建后永远不会被修改并且很有可能在Gen2堆中着陆,那么您可能最好先采取额外的分配ToArray

总的来说,虽然我发现这是罕见的情况。更常见的是看到很多ToArray调用立即传递给其他短暂的内存使用,在这种情况下ToList显然更好。

这里的关键是剖析,剖析,然后再详细介绍一些。


查看完整回答
反对 回复 2019-08-06
?
ibeautiful

TA贡献1993条经验 获得超5个赞

性能差异将是微不足道的,因为它List<T>是作为动态大小的阵列实现的。调用ToArray()(使用内部Buffer<T>类来增长数组)或ToList()(调用List<T>(IEnumerable<T>)构造函数)将最终成为将它们放入数组并增长数组直到它适合所有数组的问题。

如果您希望具体确认这一事实,请查看Reflector中相关方法的实现 - 您将看到它们归结为几乎完全相同的代码。


查看完整回答
反对 回复 2019-08-06
?
慕桂英4014372

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<>更新。)


查看完整回答
反对 回复 2019-08-06
  • 3 回答
  • 0 关注
  • 1227 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信