我对枚举器的工作方式和LINQ有一些疑问。考虑这两个简单的选择:
1 2 3 4
| List<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct().ToList(); |
或
1 2 3 4
| IEnumerable<Animal> sel = (from animal in Animals
join race in Species
on animal.SpeciesKey equals race.SpeciesKey
select animal).Distinct(); |
我更改了原始对象的名称,使其看起来像一个更通用的示例。查询本身并不那么重要。我想问的是:
1
| foreach (Animal animal in sel) { /*do stuff*/ } |
我注意到,如果我使用IEnumerable,当我调试和检查"sel"(在这种情况下是IEnumerable)时,它有一些有趣的成员:"inner"、"outer"、"innerkeyselector"和"outerkeyselector",最后2个看起来是委托。"内在"成员没有"动物"的例子,而是"物种"的例子,这对我来说很奇怪。"外部"成员确实包含"动物"实例。我想这两位代表决定了哪一位代表进入,哪一位代表退出?
我注意到,如果我使用"distinct","inner"包含6个项目(这是不正确的,因为只有2个项目是不同的),"outer"包含正确的值。同样,可能是委托方法决定了这一点,但这比我对IEnumerable的了解要多一些。
最重要的是,这两个选项中哪一个是性能最佳的?
通过.ToList()进行的邪恶名单转换?
或者直接使用枚举器?
如果可以的话,请解释一下,或者抛出一些链接来解释IEnumerable的这种用法。
IEnumerabledescribes战略行为,但这是一个执行的行为。当你使用IEnumerable,给你一个机会去编译工作Defer直到后来,possibly沿着优化的方法。如果你使用你的tolist(力)的两个reify编译结果了吧。
每当我在"stacking"LINQ表达式中使用,只有IEnumerable,因为城市的行为specifying LINQ提供一个机会去possibly OPTIMIZE Defer和评价的程序。我记得我没有LINQ to Generate SQL的两个查询的数据库,直到你枚举它吗?考虑这个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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;
} |
现在你有一个方法,selects初始样品("allspotted"),加上一些滤波器。所以现在你可以这样做:
1 2
| var Leopards = Feline(AllSpotted());
var Hyenas = Canine(AllSpotted()); |
所以它是禁食两IEnumerable使用清单吗?如果你想要的只是一个查询,从预防executed有超过一次。但它是更好的整体。嗯,在以上,leopards get和hyenas转换成SQL查询每个单身,和只读数据库返回的行,是相关的。但如果我们返回一个仇恨列表,从AllSpotted(),那么它可能运行的数据库可能因为slower回报父亲更多的数据比我需要的冰,和我们的废物循环做滤波的客户端。
在一个程序,它可能是更好的两个查询Defer转换你的诡计,直到两个极比,所以如果我要枚举的通leopards和hyenas超过一次,这样做的:
1 2
| List<Animals> Leopards = Feline(AllSpotted()).ToList();
List<Animals> Hyenas = Canine(AllSpotted()).ToList(); |
- 你好,谢谢你的回答::-)。你给了我一个很好的例子,当一个IEnumerable案例明显具有性能优势时,它是如何实现的。你知道我问题的另一部分吗?为什么可枚举项"拆分"为"内部"和"外部"?当我通过鼠标在调试/中断模式下检查元素时会发生这种情况。这可能是Visual Studio的贡献吗?在现场枚举并指示枚举的输入和输出?
- 我认为它们指的是连接的两边。如果您执行"从动物中选择*加入物种…",那么连接的内部部分是动物,外部部分是物种。
- 如果需要,还可以使用LINQ中的编译查询进一步优化代码。:)
- 当我读到关于:IEnumerablevs iqueryable的答案时,我看到了类似的解释,因此IEnumerable自动强制运行时使用Linq to对象查询集合。所以我很困惑这三种类型。stackoverflow.com/questions/2876616/&hellip;
- 对不起,我有点困惑!您的意思是说var leopards=feline(all spotted());将在一次执行两个过滤器的同时从数据库中获取数据,而不是先获取所有的斑点,然后只获取猫的斑点??
- @当添加并调用.tolist()时,除此之外的lakshay correct将执行。
- @布朗克,你所联系的答案是真的。在第一部分之后,IEnumerable将对物体进行LINQ,这意味着所有被发现的物体都必须返回以运行猫科动物。另一方面,一个IQuertable将允许对查询进行优化,只下拉斑点猫。
- 这个答案很误导人!@内特的评论解释了原因。如果您使用的是IEnumerable,那么无论发生什么,筛选都将在客户端发生。
- 另一个替代IEnumerable的方法是IReadOnlyCollection。我之所以提到这个,是因为IEnumerable可以指一个无限大的序列,而IReadOnlyCollection的序列大小是不确定的。是的,IReadOnlyCollection是一个IEnumerable,但至少你知道,当你得到一个IReadOnlyCollection时,它是某种类型的集合,你可以多次迭代,而当你得到一个IEnumerable时,你可能不太确定(重新迭代时它可能会对性能产生巨大影响)。
- @那么这是否意味着AllSpotted查询将针对数据库运行两次?或者只对它运行一次canine(),并从内存中检索到它?
- 是的,AllSpotted()将运行两次。这个答案的更大问题是下面的语句:"在上面,豹和鬣狗被转换成每个单独的SQL查询,数据库只返回相关的行。"这是错误的,因为在IEnumerable<>上调用了WHERE子句,它只知道如何循环遍历已读的对象。Y来自数据库。如果将allspotted()和feline()和canine()的参数返回到iqueryable中,那么过滤器将在SQL中发生,并且这个答案是有意义的。
有一条很好的书面市:Claudio贝尔纳斯科尼的techblog这里:当使用IEnumerable,ICollection,IList和战略
这里是一些基础知识点的情景和功能:
- 应该指出,本文只针对面向公共的代码部分,而不是内部工作。List是IList的一种实现,因此在IList中具有额外的功能(例如Sort、Find、InsertRange)。如果你强迫自己在List上使用IList的话,你可以根据自己的需要,放松这些方法。
- 别忘了IReadOnlyCollection。
实现IEnumerable的类允许您使用foreach语法。
基本上,它有一个方法来获取集合中的下一个项。它不需要整个集合在内存中,也不知道其中有多少项,foreach只是不断地获取下一项,直到用完为止。
这在某些情况下非常有用,例如在大型数据库表中,您不希望在开始处理行之前将整个内容复制到内存中。
现在,List实现IEnumerable,但表示内存中的整个集合。如果您有一个IEnumerable,并且您调用.ToList(),那么您将创建一个新列表,其中包含内存中枚举的内容。
Linq表达式返回一个枚举,默认情况下,当使用foreach迭代时,该表达式将执行。当您迭代foreach时,会执行IEnumerablelinq语句,但您可以强制它使用.ToList()更快地迭代。
我的意思是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 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 ) ... |
- 但是,如果在不首先将foreach转换为列表的情况下对ienumerable执行foreach,会发生什么?它会把整个收藏带到记忆中吗?或者,它是否在遍历foreach循环时逐个实例化元素?谢谢
- @pap后者:它再次执行,内存中不会自动缓存任何内容。
- 似乎关键的区别在于:1)记忆中是否有完整的东西。2)IEnumerable让我使用foreach,而list将按say index排序。现在,如果我想事先知道thing的计数/长度,IEnumerable就帮不了忙了,对吗?
- @jeb50不完全是-List和Array都实现IEnumerable。您可以将IEnumerable看作是最低的公分母,既适用于内存中的集合,也适用于一次获取一个项的大型集合。当你打电话给IEnumerable.Count()时,你可能是在打电话给一个快速的.Length房地产,或者是在进行整个收集工作——关键是你不知道IEnumerable的情况。这可能是一个问题,但是如果你只是去foreach,那么你就不在乎了——你的代码将与Array或DataReader相同。
- 获取下一个项目直到它用完,这是否意味着要为每个项目向数据库发送查询?
- @mfouadkajj我不知道您使用的堆栈是什么,但几乎可以肯定,它不会对每一行发出请求。服务器运行查询并计算结果集的起始点,但没有得到全部结果。对于较小的结果集,这可能是一次单独的访问,对于较大的结果集,您将发送一个请求,请求从结果中获得更多行,但不会重新运行整个查询。
上述关键差一人,ironically回答对一个问题作为一个duplicated封闭它。
IEnumerable is read-only and List is not.
在实际的战略和IEnumerable类型之间的差异
- 作为后续工作,这是因为界面方面还是因为列表方面?也就是说,ilist也是只读的吗?
- IList不是只读的-docs.microsoft.com/en-us/dotnet/api/&hellip;IEnumerable是只读的,因为它缺少在构造后添加或删除任何内容的任何方法,它是IList扩展的基本接口之一(请参阅链接)
要认识到的最重要的一点是,使用LINQ不会立即对查询进行评估。它只作为迭代生成的IEnumerable在foreach中的一部分运行——这就是所有奇怪的代表所做的。
因此,第一个示例通过调用ToList并将查询结果放入列表中,立即对查询进行评估。第二个示例返回一个IEnumerable,其中包含稍后运行查询所需的所有信息。
就性能而言,答案是视情况而定。如果您需要立即评估结果(例如,您正在改变稍后查询的结构,或者如果您不希望IEnumerable上的迭代花费很长时间),请使用列表。否则使用IEnumerable。默认情况下应该在第二个示例中使用按需评估,因为它通常使用较少的内存,除非有特定的原因将结果存储在列表中。
- 你好,谢谢你的回答::-)。这几乎消除了我所有的疑虑。你知道为什么可枚举词"分裂"成"内部"和"外部"吗?当我通过鼠标在调试/中断模式下检查元素时会发生这种情况。这可能是Visual Studio的贡献吗?在现场枚举并指示枚举的输入和输出?
- 这就是Join所做的工作——内部和外部是连接的两面。一般来说,不要担心IEnumerables中的实际内容,因为它将完全不同于您的实际代码。只有在迭代实际输出时才担心它:)
IEnumerable的优点是延迟执行(通常使用数据库)。在实际循环访问数据之前,不会执行查询。这是一个等待到需要时的查询(又称懒惰加载)。
如果您调用tolist,查询将被执行,或者如我所说的"物化"。
两者都有利弊。如果您调用tolist,您可以消除一些关于何时执行查询的秘密。如果你坚持使用IEnumerable,你会得到这样的好处:程序在实际需要之前不会做任何工作。
在将一misused概念股在下跌到那一天。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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() ); |
预期结果
1 2 3
| // I was expecting
print( startingWith_M.ToList() ); // mercedes, mazda
print( startingWith_F.ToList() ); // fiat, ferrari |
实际结果
1 2 3
| // what printed actualy
print( startingWith_M.ToList() ); // mazda
print( startingWith_F.ToList() ); // ford, fiat, ferrari |
解释
根据对方的回答,评价结果是deferred ToList直到电话或类似的方法ToArrayinvocation实例。
所以我重写的代码在本案例为:
1 2 3 4 5 6 7 8 9 10 11 12
| 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() ); |
arround播放
http:/ / / / 0 repl.it e8ki
- 这是因为Linq方法(扩展)在本例中来自IEnumerable,它只创建一个查询,而不执行它(在后台使用表达式树)。这样,您就可以在不接触数据的情况下(在本例中,是列表中的数据)对该查询执行许多操作。list方法获取准备好的查询并对数据源执行它。
- 事实上,我读了所有的答案,而你的答案是我投了赞成票的那个,因为它清楚地说明了这两个答案之间的区别,而没有特别提到LINQ/SQL。在进入Linq/SQL之前,必须了解所有这些信息。钦佩。
- 这是一个很重要的区别,但你的"预期结果"不是真的预期。你说的好像是某种"抓住"而不是设计。
- @尼姆,是的,在我了解IEnumerable是如何工作之前,这是我的期望,但现在我知道如何工作了,就不多了。
如果您只想枚举它们,请使用IEnumerable。
不过,要注意,更改正在枚举的原始集合是一个危险的操作——在本例中,您将首先希望ToList。这将为内存中的每个元素创建一个新的列表元素,枚举IEnumerable,因此,如果只枚举一次,那么性能会降低,但更安全,有时List方法很方便(例如在随机访问中)。
- 我不确定是否安全地说,生成列表意味着更低的性能。
- @史蒂文:的确,正如科普和克里斯所说,有时可能需要使用一个列表。在我的例子中,我得出的结论是不是。@daren:你所说的"这将为内存中的每个元素创建一个新的列表"是什么意思?也许你的意思是"列表条目"?::-)
- @Axonn是的,列表条目。固定的。
- @Steven如果您计划遍历IEnumerable中的元素,那么首先创建一个列表(并遍历该列表)意味着您要遍历元素两次。因此,除非您希望执行列表中效率更高的操作,否则这确实意味着性能更低。
- 假设我们只迭代一次所有的结果,除非(如您所说)操作从随机访问中受益,否则创建一个列表是没有好处的。生成这个列表总是要花我们一些钱。我的想法是,如果这是linq-to-sql,或者处理不是很简单,那么将结果缓存到一个列表中可以让我们支付一次费用,然后按照我们希望的低成本进行迭代。由于列表生成的开销相当低,因此不难找到收益大于成本的情况。我希望这能解释我的想法。
- @史蒂文认为我们在争论同一点;)
- 更糟的是,我认为我们是一致的。
- @Daren——除非你被人咬了屁股,当时IEnumerable的行为不像你所期望的那样(我来这里研究它的原因是…)。我在xpathselectelements()上执行for/each,并且没有添加.ToList(),对.Remove()的子序列调用未能删除选定的xelements。仍然不清楚为什么会这样——所以要多读书了!
- @Jerhewet:修改正在迭代的序列从来不是一个好主意。坏事情会发生的。抽象会泄露。恶魔会闯入我们的空间并造成破坏。是的,.ToList()在这里起到了作用;)
在添加发布以上所有的答案,这里是我的两美分。有许多其他的比其他类型的列表,这样ICollection implements IEnumerable,ArrayList等。所以,如果我们有任何IEnumerable作为参数的方法,我们可以通过任何类型的采集功能。即,我们可以有两种操作方法对abstraction没有任何具体实施。
有很多的案例(如一个无限狡猾或狡诈的甚大)在两个IEnumerable不能转变的战略。最明显的例子是全素数,全是用他们的Facebook用户的资料,或所有的项目在eBay上。
"不同的是,"狡猾"的对象是"对仓库在这里和现在"尽头",IEnumerable对象的"工作"(只是一小时"。所以,如果我想通过所有的项目在eBay上,一个一个小时会是什么的一点小的计算机可以处理,但.tolist()"将康医药跑了我的记忆,不管是大我的电脑技能。没有电脑我市和行动本身包含这样一种巨大的金额数据。