IEnumerable vs List - 使用什么?他们是如何工作的?

问题描述 投票:598回答:10

我对枚举器如何工作以及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*/ }
  1. 我注意到,如果我使用IEnumerable,当我调试并检查“sel”时,在这种情况下是IEnumerable,它有一些有趣的成员:“inner”,“outer”,“innerKeySelector”和“outerKeySelector”,这些最后2似乎是代表。 “内部”成员中没有“Animal”实例,而是“Species”实例,这对我来说非常奇怪。 “外部”成员确实包含“Animal”实例。我假设两位代表确定哪些进入,哪些进出?
  2. 我注意到如果我使用“Distinct”,“inner”包含6个项目(这是不正确的,因为只有2个是Distinct),但“outer”确实包含正确的值。同样,委托方法可能决定了这一点,但这比我对IEnumerable的了解要多一些。
  3. 最重要的是,两种选择中的哪一种是性能最佳的?

邪恶的名单通过.ToList()转换?

或者直接使用枚举器?

如果可以,请解释一下或抛出一些解释IEnumerable使用的链接。

c# linq list ienumerable
10个回答
671
投票

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());

那么在IEnumerable上使用List会更快吗?仅当您要阻止查询多次执行时。但总体来说它更好吗?在上面,Leopards和Hyenas各自转换为单个SQL查询,数据库只返回相关的行。但是如果我们从AllSpotted()返回了一个List,那么它可能会运行得更慢,因为数据库可以返回比实际需要的更多的数据,并且我们浪费了在客户端进行过滤的循环。

在一个程序中,最好将查询推迟到列表直到最后,所以如果我要通过Leopards和Hyenas多次枚举,我会这样做:

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();

1
投票

有许多情况(例如无限列表或非常大的列表),IEnumerable无法转换为List。最明显的例子是所有素数,facebook的所有用户及其详细信息,或ebay上的所有项目。

不同之处在于“List”对象存储在“此处和此时”,而“IEnumerable”对象“一次只能运行一次”。因此,如果我正在浏览ebay上的所有项目,那么一次只能是一台小型计算机可以处理的东西,但是“.ToList()”无论我的计算机有多大,肯定会让我失去记忆。没有计算机本身可以包含和处理如此大量的数据。

[编辑] - 不用说 - 它不是“这个或那个”。通常,在同一个类中同时使用列表和IEnumerable是很有意义的。世界上没有计算机可以列出所有素数,因为根据定义,这将需要无限量的内存。但你可以很容易地想到一个包含class PrimeContainerIEnumerable<long> primes,由于显而易见的原因,它还含有SortedList<long> _primes。到目前为止计算的所有素数。要检查的下一个素数只会针对现有素数(直到平方根)运行。这样你就可以同时获得两个 - 素数(IEnumerable)和一个很好的“素数到目前为止”,这是一个非常好的近似整个(无限)列表。


134
投票

有一篇非常好的文章:Claudio Bernasconi的TechBlog:When to use IEnumerable, ICollection, IList and List

这里有一些关于场景和功能的基础知识:


123
投票

实现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 ) ...

78
投票

没有人提到一个关键的区别,具有讽刺意味的回答是一个被重复的问题。

IEnumerable是只读的而List不是。

Practical difference between List and IEnumerable


65
投票

最重要的事情是,使用Linq,查询不会立即得到评估。它只是作为在IEnumerable<T>中迭代生成的foreach的一部分运行 - 这就是所有奇怪的代表正在做的事情。

因此,第一个示例通过调用ToList并将查询结果放入列表来立即评估查询。 第二个示例返回一个IEnumerable<T>,其中包含稍后运行查询所需的所有信息。

在性能方面,答案取决于它。如果您需要立即评估结果(例如,您正在改变稍后要查询的结构,或者如果您不希望迭代IEnumerable<T>需要很长时间),请使用列表。否则使用IEnumerable<T>。默认情况下应该是在第二个示例中使用按需评估,因为通常使用较少的内存,除非有特定的原因将结果存储在列表中。


37
投票

IEnumerable的优点是延迟执行(通常使用数据库)。在实际循环数据之前,查询将不会执行。这是一个等待它需要的查询(也就是延迟加载)。

如果你调用ToList,那么查询将被执行,或者就像我想说的那样“实现”。

这两者都有利有弊。如果你调用ToList,你可能会删除一些关于何时执行查询的谜团。如果你坚持IEnumerable,你会得到这样的好处,即程序在实际需要之前不会做任何工作。


16
投票

我将分享一下我有一天陷入的误用概念:

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() );

Expected result

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari

Actual result

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari

Explanation

根据其他答案,结果的评估推迟到调用ToList或类似的调用方法,例如ToArray

所以我可以在这种情况下重写代码:

var names = new List<string> {"mercedes", "mazda", "bmw", "fiat", "ferrari"};

// updating existing list
names[0] = "ford";

// before calling ToList directly
var startingWith_M = names.Where(x => x.StartsWith("m"));

var startingWith_F = names.Where(x => x.StartsWith("f"));

print( startingWith_M.ToList() );
print( startingWith_F.ToList() );

Play arround

https://repl.it/E8Ki/0


15
投票

如果您只想枚举它们,请使用IEnumerable

但要注意,更改枚举的原始集合是一项危险的操作 - 在这种情况下,您将首先想要ToList。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable,因此如果只枚举一次则性能较差 - 但更安全,有时List方法很方便(例如在随机访问中)。


5
投票

除了上面发布的所有答案,这是我的两分钱。除了List之外还有许多其他类型实现IEnumerable,例如ICollection,ArrayList等。因此,如果我们将IEnumerable作为任何方法的参数,我们可以将任何集合类型传递给函数。即我们可以使用方法来操作抽象而不是任何特定的实现。

© www.soinside.com 2019 - 2024. All rights reserved.