我对枚举器如何工作以及LINQ有些怀疑。考虑这两个简单的选择:
List<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct().ToList();
或
IEnumerable<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct();
我更改了原始对象的名称,因此这看起来像一个更通用的示例。查询本身并不重要。我想问的是:
foreach (Animal animal in sel) { /*do stuff*/ }
我注意到如果我使用IEnumerable
,当我调试并检查“sel”时,在这种情况下是IEnumerable,它有一些有趣的成员:“inner”,“outer”,“ innerKeySelector“和”outerKeySelector“,这些最后2个似乎是委托。 “内部”成员中没有“Animal”实例,而是“Species”实例,这对我来说非常奇怪。 “外部”成员确实包含“Animal”实例。我假设两位代表确定了哪些内容以及内容是什么?
我注意到如果我使用“Distinct”,“inner”包含6个项目(这是不正确的,因为只有2个是Distinct),但“outer”确实包含正确的值。同样,委托方法可能决定了这一点,但这比我对IEnumerable的了解要多一些。
最重要的是,这两个选项中哪一个是性能最好的?
通过.ToList()
进行恶意列表转换?
或者可以直接使用枚举器?
如果可以的话,请解释一下或抛出一些解释IEnumerable使用的链接。
答案 0 :(得分:662)
IEnumerable
描述了行为,而List是该行为的实现。当您使用IEnumerable
时,您可以让编译器有机会将工作推迟到以后,可能会在此过程中进行优化。如果使用ToList(),则强制编译器立即重新生成结果。
每当我“堆叠”LINQ表达式时,我都使用IEnumerable
,因为只有指定行为我才能给LINQ推迟评估并可能优化程序。还记得LINQ如何在枚举之前不生成用于查询数据库的SQL吗?考虑一下:
public IEnumerable<Animals> AllSpotted()
{
return from a in Zoo.Animals
where a.coat.HasSpots == true
select a;
}
public IEnumerable<Animals> Feline(IEnumerable<Animals> sample)
{
return from a in sample
where a.race.Family == "Felidae"
select a;
}
public IEnumerable<Animals> Canine(IEnumerable<Animals> sample)
{
return from a in sample
where a.race.Family == "Canidae"
select a;
}
现在你有一个方法可以选择一个初始样本(“AllSpotted”),还有一些过滤器。所以现在你可以这样做:
var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());
使用List IEnumerable
更快吗?仅当您要阻止查询多次执行时。但总体来说它更好吗?在上面,Leopards和Hyenas将转换为单个SQL查询(每个),数据库只返回相关的行。但是如果我们从AllSpotted()
返回了一个List,那么它可能会运行得更慢,因为数据库可以返回比实际需要的更多的数据,并且我们浪费了在客户端进行过滤的循环。
在程序中,最好将查询推迟到列表中,直到最后,所以如果我要通过Leopards和Hyenas多次枚举,我会这样做:
List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
答案 1 :(得分:132)
有一篇非常好的文章:Claudio Bernasconi的TechBlog:When to use IEnumerable, ICollection, IList and List
这里有一些关于场景和功能的基本要点:
答案 2 :(得分:121)
实现IEnumerable
的类允许您使用foreach
语法。
基本上它有一个方法来获取集合中的下一个项目。它不需要整个集合在内存中,也不知道其中有多少项,foreach
只是不断获取下一个项目,直到它用完为止。
这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个内容复制到内存中。
现在List
实现IEnumerable
,但代表整个集合在内存中。如果您有一个IEnumerable
并且调用.ToList()
,则会在内存中创建一个包含枚举内容的新列表。
您的linq表达式返回一个枚举,默认情况下,当您使用foreach
进行迭代时,表达式会执行。迭代IEnumerable
时会执行foreach
linq语句,但您可以使用.ToList()
强制它更快地进行迭代。
这就是我的意思:
var things =
from item in BigDatabaseCall()
where ....
select item;
// this will iterate through the entire linq statement:
int count = things.Count();
// this will stop after iterating the first one, but will execute the linq again
bool hasAnyRecs = things.Any();
// this will execute the linq statement *again*
foreach( var thing in things ) ...
// this will copy the results to a list in memory
var list = things.ToList()
// this won't iterate through again, the list knows how many items are in it
int count2 = list.Count();
// this won't execute the linq statement - we have it copied to the list
foreach( var thing in list ) ...
答案 3 :(得分:76)
没有人提到一个至关重要的区别,具有讽刺意味的是,在一个被重复的问题上回答。
IEnumerable是只读的而List不是。
答案 4 :(得分:64)
最重要的事情是,使用Linq,查询不会立即得到评估。它仅作为迭代IEnumerable<T>
中生成的foreach
的一部分运行 - 这就是所有奇怪的代表正在做的事情。
因此,第一个示例通过调用ToList
并将查询结果放在列表中立即评估查询。
第二个示例返回IEnumerable<T>
,其中包含稍后运行查询所需的所有信息。
就性能而言,答案是取决于。如果您需要立即评估结果(例如,您正在改变以后查询的结构,或者如果您不希望迭代超过IEnumerable<T>
需要花费很长时间),请使用名单。否则使用IEnumerable<T>
。默认值应该是在第二个示例中使用按需评估,因为这通常使用较少的内存,除非有特定的原因将结果存储在列表中。
答案 5 :(得分:37)
IEnumerable的优点是延迟执行(通常使用数据库)。在实际循环数据之前,查询将不会执行。这是一个等待它需要的查询(也就是延迟加载)。
如果您调用ToList,查询将被执行,或者按照我的意愿“实现”。
两者都有利弊。如果你调用ToList,你可能会删除一些关于何时执行查询的谜团。如果你坚持使用IEnumerable,你就可以获得程序在实际需要之前不做任何工作的优势。
答案 6 :(得分:16)
我将分享一个被误用的概念,我有一天会陷入其中:
drop = FALSE
var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};
var startingWith_M = names.Where(x => x.StartsWith("m"));
var startingWith_F = names.Where(x => x.StartsWith("f"));
// updating existing list
names[0] = "ford";
// Guess what should be printed before continuing
print( startingWith_M.ToList() );
print( startingWith_F.ToList() );
// I was expecting
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari
根据其他答案,结果的评估推迟到调用// what printed actualy
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari
或类似的调用方法,例如ToList
。
所以我可以在这种情况下重写代码:
ToArray
答案 7 :(得分:15)
如果您只想枚举它们,请使用IEnumerable
。
请注意,更改要枚举的原始集合是一项危险的操作 - 在这种情况下,您首先需要ToList
。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable
,因此如果只枚举一次则效果较差 - 但更安全,有时List
方法很方便(例如在随机访问中) )。
答案 8 :(得分:5)
除了上面发布的所有答案,这是我的两分钱。除了List之外还有许多其他类型实现IEnumerable,例如ICollection,ArrayList等。因此,如果我们将IEnumerable作为任何方法的参数,我们可以将任何集合类型传递给函数。即我们可以使用抽象操作的方法,而不是任何特定的实现。
答案 9 :(得分:0)
在很多情况下(例如,无限列表或非常大的列表),IEnumerable无法转换为列表。最明显的例子是所有素数,所有带详细信息的facebook用户或ebay上的所有商品。
区别在于“列表”对象“在此立即存储”,而“ IEnumerable”对象“一次仅存储”。因此,如果我要遍历ebay上的所有项目,即使是一台小型计算机也可以一次处理一次,但是“ .ToList()”肯定会耗尽我的内存,无论我的计算机有多大。没有计算机本身可以包含和处理如此大量的数据。
答案 10 :(得分:-3)
我个人发现ToList()
等在这里令人困惑,定义定义意图的扩展方法使事情变得更加清晰:
namespace System.Collections.Generic {
public static IEnumerable<TSource> Materialize<TSource>(this IEnumerable<TSource> source)
{
return source.ToList();
}
}
在Materialize()
之前,您的linq表达式是惰性的-只有在需要时才可能(可能会)对它们进行评估。
Materialize()
的所有结果都可以在内存中使用时,任何其他表达式都可以在内存中使用。
dbc.Select(..).Where(..).Materialize().Select(..);