我经常遇到这样的情况:我想在声明查询的地方对查询进行求值。这通常是因为我需要对它进行多次迭代,计算成本很高。例如:
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() -在知道内存不会被分配两次的情况下安全可靠?
我同意@mquander的观点,性能差异应该是微不足道的。但是,我想对它进行基准测试,所以我这样做了——结果是微不足道的。
Testing with List<T> source:
ToArray time: 1934 ms (0.01934 ms/call), memory used: 4021 bytes/array
ToList time: 1902 ms (0.01902 ms/call), memory used: 4045 bytes/List
Testing with array source:
ToArray time: 1957 ms (0.01957 ms/call), memory used: 4021 bytes/array
ToList time: 2022 ms (0.02022 ms/call), memory used: 4045 bytes/List
每个源数组/列表有1000个元素。所以你可以看到时间和记忆的差异都可以忽略不计。
我的结论是:您还可以使用ToList(),因为List<T>提供了比数组更多的功能,除非几个字节的内存确实对您很重要。
首选ToListAsync<T>()。
在实体框架6中,这两个方法最终都调用相同的内部方法,但ToArrayAsync<T>()在最后调用list.ToArray(),实现为
T[] array = new T[_size];
Array.Copy(_items, 0, array, 0, _size);
return array;
所以ToArrayAsync<T>()有一些开销,因此ToListAsync<T>()是首选。
除非您只是需要一个数组来满足其他约束,否则您应该使用ToList。在大多数情况下,ToArray会比ToList分配更多的内存。
两者都使用数组进行存储,但是ToList有一个更灵活的约束。它需要数组至少与集合中的元素数量一样大。如果数组更大,这不是问题。但是ToArray需要数组的大小精确到元素的数量。
为了满足这个约束,ToArray通常比ToList多做一次分配。一旦它有了一个足够大的数组,它就会分配一个完全正确大小的数组,并将元素复制回该数组中。唯一可以避免这种情况的情况是当数组的增长算法恰好与需要存储的元素数量一致时(绝对是少数)。
EDIT
有几个人问我在List<T>值中有额外的未使用内存的后果。
这是一个合理的担忧。如果创建的集合寿命很长,在创建后从未被修改过,并且有很高的机会落在Gen2堆中,那么您可能会更好地预先分配额外的ToArray。
总的来说,我发现这种情况比较罕见。更常见的情况是,大量ToArray调用被立即传递给其他短期内存使用,在这种情况下,ToList显然更好。
这里的关键是分析,分析,再分析更多。