2017-04-21 163 views
-1

我的問題基本上是一個很好的編程習慣。在IEnumerable的情況下,每個項目在ToList的情況下被評估,整個集合在它開始for循環之前被迭代。 根據下面的代碼應該使用哪個函數(GetBool1 vs GetBool2)以及爲什麼。IEnumerable vs List迭代集合

public class TestListAndEnumerable1 
{ 
    public static void Test() 
    { 
     GetBool1(); 
     GetBool2(); 

     Console.ReadLine(); 
    } 

    private static void GetBool1() 
    { 
     var list = new List<int> {0,1,2,3,4,5,6,7,8,9}; 

     foreach (var item in list.Where(PrintAndEvaluate)) 
     { 
      Thread.Sleep(1000); 
     } 
    } 

    private static bool PrintAndEvaluate(int x) 
    { 
     Console.WriteLine("Hi from " + x); 
     return x%2==0; 
    } 

    private static void GetBool2() 
    { 
     List<int> list = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 

     foreach (var item in list.Where(PrintAndEvaluate).ToList()) 
     { 
      Thread.Sleep(1000); 
     } 
    } 
} 
+0

GetBool1():通過創建一個枚舉和循環,返回匹配結果和運行代碼塊。 GetBool2():創建一個枚舉器返回匹配結果,將匹配結果複製到新列表中(如果列表是值列表類型,則分配更多內存!),創建枚舉器並遍歷並運行代碼塊。你認爲哪一個更好? – john

+0

欲瞭解更多關於差異,請看[this](https://www.codeproject.com/Articles/829624/Know-your-collections-from-IEnumerable-to-List-and)...? – Noctis

回答

2

兩個循環的bahviour是不同的。在第一種情況下,控制檯將被寫入,因爲每個項目都被迭代和評估,並且在每個Console.Write之間會發生Sleep。

在控制檯寫入第二種情況也將進行評估,但這些評估將在睡之前,都發生 - 這些只發生在所有的PrintAndEvaluate電話已經完成。

第二種情況枚舉列表的成員兩次,分配並且當它這樣做分段存儲器。

如果你的問題是:「這是最有效的」,那麼答案是第一個例子,但如果你想知道「有另一種更有效的方法」,那麼只需要使用一個循環等;

for(int counter = 0 ; counter <= list.Count; counter ++) 
    { 
     if(PrintAndEvaluate(list[counter])) 
     { 
      Thread.Sleep(1000); 
     } 
    } 

這樣可以防止構造Iterator類的實例,因此不會影響堆碎片。

+0

幸運的是,你提到過_「但這些評估都會發生在睡覺之前」。這裏Sweeper的答案是什麼,即使這個問題看起來像「時間和記憶差異」這樣簡單的事情,但實際上還有更多。重要的是要記住,如果您沒有爲此調用「ToList」或任何其他「具體化」,則對基礎集合的任何更改都會反映在各個元素評估之間。所以如果新的項目被添加到睡眠之間的集合中,那些添加將會被反映出來...... –

+0

......如果您打電話給ToList,則不是這種情況。在這種情況下,無論原始集合發生什麼變化,這些變化都不會被反映出來。根據具體情況,這可能既是期望的也是不希望的,所以有時候調用ToList是可取的,而在其他情況下則不是。一個字作爲一百個,它不僅僅是時間和記憶。 –

0

GetBool1應該被使用。

這兩種方法之間的唯一區別是ToList()呼叫的存在。對?

讓我們看看ToList通話的意義通過先讀其docs

創建從一個IEnumerable<T>List<T>

這意味着當您撥打ToList時將會創建一個新列表。您可能知道,創建新列表需要時間和內存。

另一方面,GetBool1沒有ToList調用,所以它不需要太多的時間來執行。

+0

ToList的存在實際上改變了代碼執行的順序。 – PhillipH

-1

GetBool1是更好的選擇。對於option2,即使您將IEnumerable轉換爲List,當您調用foreach時,它會再次調用GetEnumerator。但差異很小。我使你的代碼的變化不大,以輸出執行時間:

public static void Test() 
     { 
      var list = new List<int>(); 
      for (int i = 0; i < 10000; i++) 
      { 
       list.Add(i); 
      } 

      GetBool1(list); 
      GetBool2(list); 
      GetBool3(list); 
      Console.ReadLine(); 
     } 

     private static void GetBool1(List<int> list) 
     { 
      System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch(); 

      watcher.Start(); 
      foreach (var item in list.Where(PrintAndEvaluate)) 
      { 
       Thread.Sleep(1); 
      } 
      watcher.Stop(); 
      Console.WriteLine("GetBool1 - {0}", watcher.ElapsedMilliseconds); 
     } 

     private static bool PrintAndEvaluate(int x) 
     { 
      return x % 2 == 0; 
     } 

     private static void GetBool2(List<int> list) 
     { 
      System.Diagnostics.Stopwatch watcher = new System.Diagnostics.Stopwatch(); 

      watcher.Start(); 
      foreach (var item in list.Where(PrintAndEvaluate).ToList()) 
      { 
       Thread.Sleep(1); 
      } 
      watcher.Stop(); 
      Console.WriteLine("GetBool2 - {0}", watcher.ElapsedMilliseconds); 
     } 

輸出是: enter image description here

+0

比較不完全相同的方法似乎毫無意義。另外,對單個迭代進行基準測試似乎是可疑的,尤其是當您甚至不先對這些方法進行JIT處理時。 – InBetween