2012-11-06 94 views
20

我有一個項目列表和一個LINQ查詢。現在,隨着LINQ的延期執行,後續的foreach循環只會執行一次查詢還是循環中的每一次執行?foreach只執行一次查詢嗎?

考慮這個例子(來自Introduction to LINQ Queries (C#), on MSDN兩者)​​

// The Three Parts of a LINQ Query: 
    // 1. Data source. 
    int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; 

    // 2. Query creation. 
    // numQuery is an IEnumerable<int> 
    var numQuery = 
     from num in numbers 
     where (num % 2) == 0 
     select num; 

    // 3. Query execution. 
    foreach (int num in numQuery) 
    { 
     Console.Write("{0,1} ", num); 
    } 

或者,換句話說,會有任何區別,如果我有

foreach (int num in numQuery.ToList()) 

而且,那就沒關係,如果底層數據不在數組中,而是在數據庫中?

回答

15

現在,隨着LINQ的延期執行,後續的foreach循環只會執行一次查詢還是每循環執行一次查詢?

是的,一次爲循環。實際上,它可能少於一次地執行查詢 - 您可以中止循環部分,並且不會對任何剩餘項目執行測試。

或者,換句話說,會不會有什麼區別,如果我有:

foreach (int num in numQuery.ToList()) 

兩點區別:

  1. 在上述情況下,ToList()浪費時間和內存,因爲它首先與最初的foreach執行相同的操作,從中建立一個列表,然後從那個列表中建立一個列表foreach。根據結果​​的大小,差異將介於微不足道和阻止代碼運行之間。

  2. 但是,如果你要反覆相同的結果做foreach,否則反覆使用的情況下,再而foreach只運行查詢一次,下foreach再次運行它。如果查詢很昂貴,那麼ToList()方法(並存儲該列表)可以是一個巨大的節約。

+0

是的。正如上面的編輯所解釋的,也許甚至不到整個查詢的執行。 –

+0

感謝您詳細解釋我! – Marcel

8

不,它沒有區別。 in表達式被評估一次。更具體而言,foreach構造調用in表達式上的GetEnumerator()方法,並重復調用MoveNext()並訪問Current屬性以遍歷IEnumerable

OTOH,呼叫ToList()是多餘的。你不應該打擾它。

如果輸入的是一個數據庫,情況稍有不同,因爲LINQ輸出IQueryable,但我敢肯定,foreach仍然將其視爲IEnumerable(其中IQueryable繼承)。

+0

...這就是爲什麼在嘗試更改集合的foreach通過時引發異常。 – Alex

+0

@Alex或嚴格來說,爲什麼它可能被拋出。 「foreach」的規則並不保證允許修改,但集合可以自由地允許它,有時必須(如果它們必須禁止來自其他線程的同時更改,則爲併發使用而設計的那些會受到阻礙)。 –

2

正如所寫,循環的每次迭代將完成儘可能多的工作,以獲取下一個結果。所以答案在技術上是「以上都不是」。該查詢將「分段」執行。

如果使用ToList()或任何其他實現方法(ToArray()等),那麼查詢將被一次當場和後續操作(如遍歷結果)評估將只是一個「啞」名單上的工作。

如果numbers是一個IQueryable而不是IEnumerable - 因爲它很可能是數據庫中的場景 - 那麼,上述仍接近真相雖然不是一個完全準確的描述。特別是,在實現結果的第一次嘗試中,可查詢提供程序將與數據庫進行通信並生成結果集;那麼,這個結果集中的行將在每次迭代中被拉動。

+0

感謝您解釋數據庫部分。 – Marcel

1

當它被列舉的LINQ查詢將被執行(無論是作爲一個.ToList()調用的結果或做foreach了結果。

如果您枚舉LINQ查詢的結果兩次,兩次都會導致它查詢數據源(在你的例子中,枚舉集合),因爲它本身只返回IEnumerable。但是,根據linq查詢,它可能並不總是枚舉整個集合(例如.Any().Single()將會如果存在.Where(),則停止在第一個對象或第一個匹配對象上)。

一個LINQ提供程序的實現細節可能有所不同,因此通常的行爲時,數據源是一個數據庫調用.ToList()馬上到cache查詢&的結果也保證了查詢(在EF的情況下, L2S或NHibernate)是,然後執行一次,然後,而不是在代碼後面的某個點枚舉集合,並且如果多次枚舉結果時阻止多次執行查詢。