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

Axo*_*onn 634 c# linq ienumerable list

我对枚举器如何工作以及LINQ有些怀疑.考虑这两个简单的选择:

List<Animal> sel = (from animal in Animals 
                    join race in Species
                    on animal.SpeciesKey equals race.SpeciesKey
                    select animal).Distinct().ToList();
Run Code Online (Sandbox Code Playgroud)

要么

IEnumerable<Animal> sel = (from animal in Animals 
                           join race in Species
                           on animal.SpeciesKey equals race.SpeciesKey
                           select animal).Distinct();
Run Code Online (Sandbox Code Playgroud)

我更改了原始对象的名称,因此这看起来像一个更通用的示例.查询本身并不重要.我想问的是:

foreach (Animal animal in sel) { /*do stuff*/ }
Run Code Online (Sandbox Code Playgroud)
  1. 我注意到,如果我使用IEnumerable,当我调试并检查"sel"时,在这种情况下是IEnumerable,它有一些有趣的成员:"inner","outer","innerKeySelector"和"outerKeySelector",这些最后2个出现成为代表."内部"成员中没有"Animal"实例,而是"Species"实例,这对我来说非常奇怪."外部"成员确实包含"Animal"实例.我假设两位代表确定哪些进入,哪些进出?

  2. 我注意到如果我使用"Distinct","inner"包含6个项目(这是不正确的,因为只有2个是Distinct),但"outer"确实包含正确的值.同样,委托方法可能决定了这一点,但这比我对IEnumerable的了解要多一些.

  3. 最重要的是,两种选择中的哪一种是性能最佳的?

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

或者直接使用枚举器?

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

Chr*_*ham 709

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;
}
Run Code Online (Sandbox Code Playgroud)

现在你有一个方法可以选择一个初始样本("AllSpotted"),还有一些过滤器.所以现在你可以这样做:

var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted());
Run Code Online (Sandbox Code Playgroud)

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

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

List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList();
Run Code Online (Sandbox Code Playgroud)

  • 这个答案非常误导!@Nate的评论解释了原因.如果你正在使用IEnumerable <T>,过滤器将在客户端发生,无论如何. (20认同)
  • 我认为他们指的是联合的两个方面.如果您执行"SELECT*FROM Animals JOIN Species ...",则连接的内部部分为Animals,外部部分为Species. (11认同)
  • 当我读到以下答案时:**IEnumerable <T> vs IQueryable <T>**我看到了类比的解释,因此IEnumerable会自动强制运行时使用LINQ to Objects来查询集合.所以我对这三种类型感到困惑.http://stackoverflow.com/questions/2876616/returning-ienumerablet-vs-iqueryablet (9认同)
  • @Bronek你联系的答案是真的.`IEnumerable <T>`将是第一部分之后的LINQ-To-Objects,意味着必须返回所有被发现的运行Feline.另一方面,`IQuertable <T>`将允许查询被细化,仅拉下Spotted Felines. (3认同)
  • 是的AllSpotted()将运行两次。这个答案的最大问题是以下语句:“以上所述,Leopards和Hyenas分别转换为单个SQL查询,而数据库仅返回相关的行。” 这是错误的,因为在IEnumerable &lt;&gt;上调用了where子句,并且该子句只知道如何遍历数据库中已有的对象。如果您将AllSpotted()以及Feline()和Canine()的参数返回到IQueryable,则过滤器将在SQL中发生,并且此答案很有意义。 (3认同)
  • 错误且具有误导性的答案。IEnumerable(只读)与yield 关键字一起使用可用于延迟执行。最好将结果缓存到列表或字典中,以便重复执行或随机访问。 (2认同)

rub*_*low 150

有一篇非常好的文章写道:Claudio Bernasconi的TechBlog在这里:何时使用IEnumerable,ICollection,IList和List

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

在此输入图像描述 在此输入图像描述

  • 应该指出的是,本文仅针对面向公众的代码部分,而不是内部工作.`List`是`IList`的一个实现,因此在`IList`中有更多的功能(例如`Sort`,`Find`,`InsertRange`).如果你强迫自己使用`IList`而不是`List`,你就会失去这些你可能需要的方法 (19认同)
  • 不要忘记`IReadOnlyCollection <T>` (3认同)
  • 在这里包含一个普通数组“[]”也可能会有所帮助。 (2认同)

Kei*_*ith 128

实现的类IEnumerable允许您使用foreach语法.

基本上它有一个方法来获取集合中的下一个项目.它不需要整个集合在内存中并且不知道其中有多少项,foreach只是不断获取下一个项目直到它用完为止.

这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个事物复制到内存中.

现在List实现IEnumerable,但代表内存中的整个集合.如果你有一个IEnumerable并且你打电话给.ToList()你创建一个新列表,其中包含内存中枚举的内容.

你的linq表达式返回一个枚举,默认情况下,当你迭代使用时,表达式会执行foreach.一个IEnumerableLINQ语句时,你遍历执行foreach,但可以迫使它更快地迭代使用.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 ) ...
Run Code Online (Sandbox Code Playgroud)

  • 但是如果你在IEnumerable**上执行foreach而没有先将它转换为List**会发生什么?它是否将整个系列带入记忆中?或者,它是否逐个实例化元素,因为它遍历foreach循环?谢谢 (2认同)

CAD*_*oke 85

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

IEnumerable是只读的而List不是.

请参阅List和IEnumerable之间的实际区别


the*_*oop 66

最重要的事情是,使用Linq,查询不会立即得到评估.它只能运行通过所产生的迭代的一部分IEnumerable<T>foreach-这就是所有的怪异的代表都在做.

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

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

  • 这就是"加入"做它的工作 - 内部和外部是连接的两个方面.一般来说,不要担心"IEnumerables"中的实际内容,因为它与您的实际代码完全不同.迭代它时只担心实际输出:) (5认同)

Mat*_*man 39

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

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

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


amd*_*amd 22

我将分享一下我有一天误入歧途的概念:

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() );
Run Code Online (Sandbox Code Playgroud)

预期结果

// I was expecting    
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari
Run Code Online (Sandbox Code Playgroud)

实际结果

// what printed actualy   
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari
Run Code Online (Sandbox Code Playgroud)

说明

根据其他答案,结果的评估被推迟到调用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() );
Run Code Online (Sandbox Code Playgroud)

玩arround

https://repl.it/E8Ki/0

  • 实际上,我阅读了所有答案,而你的答案是我赞成的答案,因为它清楚地说明了两者之间的区别,而没有专门讨论 LINQ/SQL。在使用 LINQ/SQL 之前了解所有这些是必不可少的。钦佩。 (2认同)

Dar*_*mas 15

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

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

  • @jerhewet:修改迭代的序列永远不是一个好主意.坏事会发生.抽象将泄漏.恶魔将闯入我们的维度并造成严重破坏.所以是的,`.ToList()`有帮助;) (3认同)
  • 我不确定是否可以肯定地说生成列表意味着性能较低。 (2认同)

小智 8

IEnumerable(延迟执行)的缺点是,在您调用该列表之前.ToList(),列表可能会发生更改。对于一个非常简单的例子 - 这会起作用

var persons;
using (MyEntities db = new MyEntities()) {
    persons = db.Persons.ToList(); // It's mine now. In the memory
}
// do what you want with the list of persons;
Run Code Online (Sandbox Code Playgroud)

这是行不通的

IEnumerable<Person> persons;
 using (MyEntities db = new MyEntities()) {
     persons = db.Persons; // nothing is brought until you use it;
 }

persons = persons.ToList();  // trying to use it...
// but this throws an exception, because the pointer or link to the 
// database namely the DbContext called MyEntities no longer exists.
Run Code Online (Sandbox Code Playgroud)


Ana*_*nth 5

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