這個做法也是繼續在對 Iterator Methods
與 yield return
進行延伸討論,這次是在對 Lazy Evaluation
特性進行更詳細的說明,
還有不使用 Iterator Methods
可能會導致轉換問題。
首先產生集合這個需求是非常常見的,例如在讀取完資料庫後通常需要轉換成可操作的集合,假設我有一個產生集合的方法像下面這段程式碼:
static IList<int> CreateSequence(int numberOfElements, int startAt, int stepBy)
{
var collection = new List<int>(numberOfElements);
for (int i = 0; i < numberOfElements; i++)
collection.Add(startAt + i * stepBy);
return collection;
}
了解前幾個做法的概念後可以知道,上面這段程式碼並不是 Iterator Methods
因為它沒有使用 yield return
回傳值也不是 IEnumerable<T>
,
所以上面這種寫法是常見的傳統寫法,它會先建立一個暫時的 List<int>
保存中繼結果等處理完成後進行輸出。
假設我有一個 BindingList<int>
結構,我之後使用上面的方法產生的集合進行初始化,這會導致一個問題就是建構 BindingList
所用的
CreateSequence(100,0,5).ToList()
並不會複製一份新的結果而是複製地址,所以假設同時有另一個地方使用到了同一分資料地址那可能會導致
初始化錯誤。
var data = new BindingList<int>(CreateSequence(100,0,5).ToList());
另一個問題在做法 31 有提到,CreateSequence
因為不是 Iterator Methods
所以並不具備 Continuable methods
的特性,也就是說
它一但開始運行就必須完成為止。
當然解決的方法也很簡單那就是改成 Iterator Methods
就好。
static IEnumerable<int> CreateSequence(int numberOfElements, int startAt, int stepBy)
{
for (var i = 0; i < numberOfElements; i++)
yield return startAt + i * stepBy;
}
另外要提一下下面這兩種寫法,第一種回傳的是 IEnumerable<T>
所以建構 List<int>
時所用的建構函式使用的是 IEnumerable<T>
的多載版本,
第二種則是會先把呼叫 ToList() 方法把結果轉換成 List
這樣感覺起來效能比較差,但其實 BindingList
會直接使用剛剛建立的 List
的參考,
所以不會再完整複製一次,所以效能不會比較差。
var listStorage = new List<int>(CreateSequence(100, 0, 5));
var data = new BindingList<int>(CreateSequence(100,0,5).ToList());
最後又對 Continuable methods
這個特性提出一個範例,也就是本做法要提出的重點,下面這段程式碼展現出可中斷的特性,
雖然 CreateSequence
會建立一個 10000 個元素的集合,但是關鍵在最後的 FirstOrDefault
代表我們只需要第一個元素即可,
如果使用舊的寫法那會先把這 10000 個元素的集合先產生完成之後在挑出第一個。
但是在 yield return
的版本中透過中斷的特性,迴圈就真的只會執行一次,剩下的 9999 次都可以省略掉了。
void Main()
{
var sequence = CreateSequence(10000, 0, 7).FirstOrDefault();
}
static IEnumerable<int> CreateSequence(int numberOfElements, int startAt, int stepBy)
{
for (var i = 0; i < numberOfElements; i++)
yield return startAt + i * stepBy;
}
Summary
這個做法將 Lazy Evaluation
特性進行解說,好處可以等待真正需要用到值的時候才會執行該方法,
基本上都是對過去提到的內容提出範例與補充說明,主要還是在建議使用者多使用 yield return
。