2017-10-20 84 views
-1

我知道一个IEnumerable<T>不会迭代,直到它调用。在这种情况下订购了多少次IEnumerable?

假设我有这样的代码:

foreach(int iteratorInt in myIEnumerable.OrderBy(x => x)) 
{ 
    if(iteratorInt == myIEnumerable.First()) 
    { 
     // do something 
    } 
} 

if我检查的第一要素,所以myIEnumerable必须订购每次迭代,看看哪个是第一要素,或者是有序只有一次?

+1

只需一次,这就是'for'循环的工作原理。 – DavidG

+1

这不会编译,因为'myIEnumerable.OrderBy(x => x.MyProperty)'返回'IEnumerable '而不是'IEnumerable '。 –

+0

@TimSchmelter谢谢,我已更改为OrderBy(x => x)。那是一个错误。无论如何,这只是一个例子,表明我正在订购并且稍后检查第一个元素。 –

回答

1

当使用LINQ扩展时,查询将只在请求时执行,否则称为延迟执行。当多次请求相同的查询时,每次都会重新评估基础查询,除非初始查询已使用类似.ToArrary().ToList()的物件化。

这个问题还不完全清楚,所以我会举几个例子来说明各种行为。

实施例1:

  • 设置为一个局部变量的初始请求。
  • 在foreach中应用LINQ查询来订购集合。
  • 使用初始局部变量来查找第一个结果。
  • 不要实现任何结果。

代码:

private static void Ex1() 
{ 
    Console.WriteLine("A"); 

    IEnumerable<int> myIEnumerable = GetEnumerable(); 

    Console.WriteLine("B"); 

    foreach (int i in myIEnumerable.OrderBy(x => x)) 
    { 
     Console.WriteLine("*** foreach : " + i); 
     if (i == myIEnumerable.First()) 
     { 
      Console.WriteLine("=== Matched .First() : " + i); 
     } 
    } 

    Console.WriteLine("C"); 
} 

出2:

  • 设置为一个局部变量的初始请求。
  • 在foreach外部应用LINQ查询来订购集合而不实现结果。
  • 使用有序查询找到第一个结果
  • 不要实现任何结果。

代码:

private static void Ex2() 
{ 
    Console.WriteLine("A"); 

    IEnumerable<int> myIEnumerable = GetEnumerable(); 

    Console.WriteLine("B"); 

    var ordered = myIEnumerable.OrderBy(x => x); 

    foreach (int i in ordered) 
    { 
     Console.WriteLine("*** foreach : " + i); 
     if (i == ordered.First()) 
     { 
      Console.WriteLine("=== Matched .First() : " + i); 
     } 
    } 

    Console.WriteLine("C"); 
} 

出3:

  • 设置为一个局部变量的初始请求。
  • 在foreach外部应用LINQ查询来订购集合并实现结果。
  • 使用有序查询来查找第一个结果。

代码:

private static void Ex3() 
{ 
    Console.WriteLine("A"); 

    IEnumerable<int> myIEnumerable = GetEnumerable(); 

    Console.WriteLine("B"); 

    var ordered = myIEnumerable.OrderBy(x => x).ToArray(); 

    foreach (int i in ordered) 
    { 
     Console.WriteLine("*** foreach : " + i); 
     if (i == ordered.First()) 
     { 
      Console.WriteLine("=== Matched .First() : " + i); 
     } 
    } 

    Console.WriteLine("C"); 
} 

所有查询使用相同的方法来获取枚举:

private static IEnumerable<int> GetEnumerable() 
{ 
    Console.WriteLine("~~~ GetEnumerable Start"); 
    foreach (int i in new[]{3, 2, 1}) 
    { 
     Console.WriteLine(">>> yield return : " + i); 
     yield return i; 
    } 

    Console.WriteLine("~~~ GetEnumerable End"); 
} 

结果将最终成为:

==================== 
Ex A 
==================== 
A 
B 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
*** foreach : 1 
~~~ GetEnumerable Start 
>>> yield return : 3 
*** foreach : 2 
~~~ GetEnumerable Start 
>>> yield return : 3 
*** foreach : 3 
~~~ GetEnumerable Start 
>>> yield return : 3 
=== Matched .First() : 3 
C 

==================== 
Ex B 
==================== 

A 
B 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
*** foreach : 1 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
=== Matched .First() : 1 
*** foreach : 2 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
*** foreach : 3 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
C 

==================== 
Ex C 
==================== 

A 
B 
~~~ GetEnumerable Start 
>>> yield return : 3 
>>> yield return : 2 
>>> yield return : 1 
~~~ GetEnumerable End 
*** foreach : 1 
=== Matched .First() : 1 
*** foreach : 2 
*** foreach : 3 
C 
0

此代码:

foreach(int iteratorInt in myIEnumerable.OrderBy(x => x.MyProperty)) 

OrderBy只执行一次

1

你可枚举将责令只有一次,在这里:myIEnumerable.OrderBy(x => x)
在这条线if(iteratorInt == myIEnumerable.First())它不会被再次订购。

也许您误解了IEnumerable.First方法,IEnumerable.FirstIEnumerable.OrderBy方法之间没有关系。

Console.WriteLine(new [] {3, 2, 1}.First()); 
// here you get 3, not 1.  

这里你可以看到一个自定义OrderBy方法:

using System; 
using System.Collections.Generic; 
using System.Linq; 

public class Program 
{ 
    public static void Main() 
    { 
     var myIEnumerable = GetMyEnumerable(); 
     foreach(var item in myIEnumerable.MyCustomOrderBy(x => x)) 
     {   
      if(item == myIEnumerable.First()) 
      { 
       Console.WriteLine("The condition is true with: " + item); 
      } 
     } 
    } 

    public static IEnumerable<int> GetMyEnumerable() 
    {   
     foreach(var i in new int[] {5, 4, 3, 2, 1}) 
     { 
      Console.WriteLine("GetMyEnumerable was called " + i); 
      yield return i; 
     }  
    }  
} 

public static class OrderByExtensionMethod 
{ 
    public static IOrderedEnumerable<TSource> MyCustomOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
    { 
     Console.WriteLine("OrderByExtensionMethod was called"); 
     return source.OrderBy(keySelector); 
    } 
} 

输出:

OrderByExtensionMethod was called 
GetMyEnumerable was called 5 
GetMyEnumerable was called 4 
GetMyEnumerable was called 3 
GetMyEnumerable was called 2 
GetMyEnumerable was called 1 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
The condition is true with: 5  

发生了什么?
首先调用MyCustomOrderBy方法,他需要遍历整个集合来对元素进行排序。

OrderByExtensionMethod was called 
GetMyEnumerable was called 5 
GetMyEnumerable was called 4 
GetMyEnumerable was called 3 
GetMyEnumerable was called 2 
GetMyEnumerable was called 1 

然后你的foreach开始,myIEnumerable.First()是根据每个项目执行:

GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 
GetMyEnumerable was called 5 

最后,你会得到你想要的东西:

The condition is true with: 5 
+1

您可能需要考虑将'for'循环更改为'foreach(var Item in GetMyEnumerable()。OrderBy(x => x))'',以更好地匹配问题,并显示'OrderBy'不发生不止一次。 –

0

OrderBy只计算一次,但是,将在每次迭代中创建基于原始无序myIEnumerable的新的IEnumerator<T>,并且它可能不匹配iteratorInt在第一次迭代,除非第一个元素恰好是排序到第一个位置。

如果你想要第一次迭代的值iteratorInt匹配可枚举的First()结果。你想在循环之前创建有序枚举的临时副本,就像这样:

var list = myIEnumerable.OrderBy(x => x); 
foreach(int iteratorInt in list) 
{ 
    if(iteratorInt == list.First()) 
    { 
     // do something 
    } 
} 

虽然这是一个相当没有意义的模式(类似于“Loop-switch”反模式),因为它可以简化为:

//do something with list.First();