2017-10-20 87 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();