一个IENumerable被调用了多少次?

4

我知道当我遍历 IEnumerable 时,我遍历的是源集合,因此如果我在下一次 IEnumerable 的迭代中修改了源集合,它会计算修改。

所以我想知道,当我有一个排序过的 IEnumerable 时,它会如何影响。

例如,如果我有这个:

List<MyType> myNoOrderedList //add my items

IEnumerable<MyType> myOrderedIEnumerable = myNoOrderedList
  .OrderBy(x => x.Property);

foreach(MyType in myOrderedIEnumerable)
{
    //Do something
}

假设我有一个包含3个元素的列表,在IEnumerable的每次迭代中,列表是否被排序或只有一次排序?
如果在“做某事”中添加或删除项目会发生什么?IEnumerable是否具有最初排序的项目,还是必须重新排序以考虑列表的修改?

据我所知,LINQ 生成的可枚举对象每次都会重新枚举,因此如果您修改了基本列表并进行迭代,则顺序仍将正确。但是,自己检查这一点非常简单。 - apokryfos
1
只需运行代码并自行检查,比在这里询问要快得多 ;) - Fabio
2
如果在“做某事”中添加或删除一个项目会发生什么?当您运行它时,您将以异常的格式获得非常快速的答案。 - Fabio
1
https://dotnetfiddle.net/dmRx2q - apokryfos
3个回答

5

答案:

  1. 最多一次(在任何实体化情况下)
  2. 由于您已经将myOrderedIEnumerable实体化,因此不会看到初始myNoOrderedList的任何修改:

简化例子:

 List<string> initial = new List<String>() {
   "a", "z", "e", "d";
 };

 // Nothing will be done at this point (no ordering)
 var ordered = initial.
   .OrderBy(x => x.Property);

 // ordered has not materialized here, so it'll feel Addition
 initial.Add("y"); // <- will be added into ordered 

 // Here on the first loop ordered will be materialized and since
 // initial and ordered are different collection now, we can modify 
 // initial without changing ordered
 foreach (var item in ordered) {
   if (item == "a") {
     initial.Add("b");
     initial.Remove("z");
   } 

   Console.WriteLine(item);
 }

结果:

a
d
e
y <- before materialization
z 

编辑: 请注意,实现材料化是一件棘手的事情:它可能被称为:

  1. 立即,在声明后,例如在 .ToList(), .Any(), .FirstOrDefault() 之后。
  2. 在第一个项目上,例如 .OrderBy (您的情况)。
  3. 从不,例如 .Where(), .SkipWhile() - 请参见 Magnus 的评论。

Linq 是惰性的,并尽可能晚地执行材料化。


1
但是,如果查询看起来不同,例如只包含where,它将影响foreach循环。因此,“物化”根据查询的不同而表现不同。 - Magnus
你好Dmitry,你能否将这个答案扩展到多线程场景或至少添加一条评论吗?非常感谢! - Sharky
@Sharky:坏消息List<T>initial)不是线程安全的,这就是为什么多线程场景不适用于它(如果您尝试修改列表,您将得到不稳定的结果)。 - Dmitry Bychenko
@Magnus:你说得很对,谢谢!Linq是惰性的,这就是为什么nmaterialization很棘手;这个事实值得一提。 - Dmitry Bychenko
@DmitryBychenko 谢谢您的回复,Dmitry! - Sharky

2
假设列表中有3个元素,在IEnumerable的每次迭代中,列表是否有序,还是只有一次排序?
根据您当前的代码,只有一次排序。每次获取枚举器时都会对列表进行排序。您的foreach语句将被编译为try..finally,只有一个枚举器用于遍历集合。
如果我在“做某事”中添加或删除项目会发生什么?IEnumerable具有初始有序项还是必须重新排序以考虑列表的修改?
如果在从IOrderedEnumerable获取枚举器之前从myNoOrderedList中添加或删除项目,则它将包含在排序结果中。如果在开始枚举排序集合后执行此操作,则不会以任何方式影响您的活动枚举,因为已缓冲并从缓冲区返回排序项。
但是,请记住,如果您可以向正在枚举的集合(在您的情况下为miOrderedIEnumerable)中添加或删除项目,则会收到InvalidOperationException,指出“已修改集合;可能无法执行枚举操作”。

1
抱歉,但第二个答案不正确。您不能在foreach内更改miOrderedIEnumerable,但可以在myNoOrderedList上调用AddRemoveAtClear等方法。但是,当修改myNoOrderedList时,miOrderedIEnumerable将不会被更改。 - Dmitry Bychenko
@DmitryBychenko 当然,你是对的,我的错 :( 我已经更新了我的答案。 - Yeldar Kurmangaliyev

0
每次需要使用 OrderBy 的结果时,都会对 源的缓冲副本进行排序。例如:
int[] array = { 2, 1, 3 };
var ordered = array.OrderBy(x => x);

foreach (int i in ordered)
{
    array[1] = 0;
    Debug.Write(i);             // 123
}

foreach (int i in ordered)
    Debug.Write(i);             // 023

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接