我正在使用一些LINQ选择内容来创建一些返回IEnumerable<T>
的集合。
在我的情况下,我需要一个List<T>
,所以我将结果传递给List<T>
的构造函数来创建一个。
我想知道这样做的开销。我的馆藏中的项目通常是数百万,所以我需要考虑这个。
我认为,如果IEnumerable<T>
包含ValueTypes
,则表现最差。
我是对的吗? Ref
类型怎么样?无论哪种方式,还有拨打费用,List<T>.Add
一百万次,对吧?
有什么方法可以解决这个问题?就像我可以使用扩展方法“重载”LINQ Select这样的方法吗?
答案 0 :(得分:6)
最好避免使用列表。如果你可以使用IEnumerable&lt; T&gt;来保持你的来电者,你会省去一些麻烦。
LINQ的ToList()将获取您的可枚举,并且只构造一个新的List&lt; T&gt;直接使用List&lt; T&gt;(IEnumerable&lt; T&gt;)构造函数。这与自己制作列表相同,性能明智(尽管LINQ也进行了空检查)。
如果您自己添加元素,请使用AddRange方法而不是Add。 ToList()与AddRange非常相似(因为它使用的构造函数采用IEnumerable&lt; T&gt;),在这种情况下,这通常是你最好的选择,性能明智。
答案 1 :(得分:6)
不,假设您使用IEnumerable<T>
而不是IEnumerable
,元素类型作为值类型没有特别的惩罚。你不会得到任何拳击。
如果实际预先知道结果的大小(Select
的结果可能不会),您可能要考虑使用该大小的缓冲区创建列表,然后使用AddRange
添加值。否则,每次填充时,列表都必须调整缓冲区的大小。
例如,而不是:
Foo[] foo = new Foo[100];
IEnumerable<string> query = foo.Select(foo => foo.Name);
List<string> queryList = new List<string>(query);
你可能会这样做:
Foo[] foo = new Foo[100];
IEnumerable<string> query = foo.Select(x => x.Name);
List<string> queryList = new List<string>(foo.Length);
queryList.AddRange(query);
您知道调用Select
将产生与原始查询源相同长度的序列,但据我所知,执行环境中没有任何内容具有该信息。
答案 2 :(得分:1)
不要将IEnumerable传递给List构造函数。 IEnumerable有一个ToList()方法,它不可能做得更糟,并且语法更好(恕我直言)。
那就是说,这只会将你的问题的答案改为“它取决于” - 特别是,它取决于IEnumerable实际上是幕后的内容。如果它恰好是List,那么ToList 实际上是免费的,当然比它是另一种类型要快得多。它仍然没有超快。
当然,解决此问题的最佳方法是尝试弄清楚如何在IEnumerable而不是List上进行处理。这可能是不可能的。
编辑:评论中的一些人正在辩论ToList()在List上调用时实际上是否会比没有更快,并且ToList()是否会比列表构造函数更快。在这一点上,推测变得毫无意义,所以这里有一些代码:
using System;
using System.Linq;
using System.Collections.Generic;
public static class ToListTest
{
public static int Main(string[] args)
{
List<int> intlist = new List<int>();
for (int i = 0; i < 1000000; i++)
intlist.Add(i);
IEnumerable<int> intenum = intlist;
for (int i = 0; i < 1000; i++)
{
List<int> foo = intenum.ToList();
}
return 0;
}
}
使用IEnumerable运行此代码实际上是一个List比使用LinkedList或Stack替换它快6到10倍(在我的pokey 2.4 GHz P4上,使用Mono 1.2.6)。可以想象,这可能是由于ToList()与LinkedList或Stack的枚举的特定实现之间的一些不幸的交互,但至少还有一点:速度将取决于IEnumerable的基础类型。也就是说,即使使用List作为源代码,我仍然需要6秒才能进行1000个ToList()调用,所以它远非免费。
接下来的问题是ToList()是否比List构造函数更智能。答案结果证明是:List构造函数与ToList()一样快。事后看来,Jon Skeet的推理是有道理的 - 我只是忘记了ToList()是一种扩展方法。我仍然(很多)更喜欢ToList()语法,但没有使用它的性能原因。
所以简短的版本是最好的答案仍然是“如果可以避免它就不要转换为List”。除此之外,实际表现将大大取决于IEnumerable实际上是什么,但充其量只是缓慢,而不是冰川。我修改了原来的答案以反映这一点。
答案 3 :(得分:1)
一般来说,返回IEnumerable
的方法不必在实际需要项目之前评估任何项目。所以,从理论上讲,当你返回IEnumerable
时,你们当中没有任何物品需要存在。
因此创建一个列表意味着你真的需要评估项目,获取它们并将它们放在内存中(至少是它们的引用)。没有什么可以做的 - 如果真的需要有一个列表。
答案 4 :(得分:1)
其他一些响应者已经提出了如何提高将IEnumerable<T>
复制到List<T>
的效果的想法 - 我认为在这方面不能添加太多。
然而,根据你所描述的你需要对结果做的事情,以及当你完成时你摆脱列表的事实(我认为这意味着中间结果不是很有趣) - 你可以想要考虑是否真的需要实现List<T>
。
考虑为List<T>
编写一个执行相同处理逻辑的延迟扩展方法,而不是创建IEnumerable<T>
并对该列表的内容进行操作。我在很多情况下都是这样做的,在使用编译器支持的[yield return][1]
语法时,用C#编写这样的逻辑并不是那么糟糕。
如果你要做的就是访问结果中的每个项目并从中收集一些信息,这种方法很有效。通常,您需要做的只是按需访问集合中的每个元素,使用它进行一些处理,然后继续。这种方法通常具有更高的可扩展性和性能,可以创建集合的副本,只是为了迭代它。
现在,由于其他原因,此建议可能对您不起作用,但值得考虑作为替代方法,找到实现非常大的列表的最有效方法。
答案 5 :(得分:1)
通过阅读各种评论和问题,我得到以下要求
对于需要在该集合中运行的数据集合,过滤掉一些对象,然后对其余对象执行一些转换。如果是这样的话,你可以这样做:
var result = from item in collection
where item.Id > 10 //or some more sensible condition
select Operation(item);
如果您需要执行更多过滤和转换,您可以嵌套LINQ查询,如
var result = from filteredItem in (from item in collection
where item.Id > 10 //or some more sensible condition
select Operation(item))
where filteredItem.SomePropertyAvailableAfterFirstTransformation == "new"
select SecondTransfomation(filteredItem);