Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
作者提出兩種取得集合枚舉的方式,分別為 Lazy Evaluation(延後取得)、Eager Evaluation(立即取得)。兩種方式各有其適用情境,一般而言, Lazy Evaluation 會適用於大部分的情境。
首先必須了解 Lazy Evaluation 的產生方式,以下用一個簡單的範例演示。
public static IEnumerable<TResult> Generate<TResult>(
int number,
Func<TResult> generator )
{
for ( int i = 0; i < number; i++ )
yield return generator( );
}
測試程式碼:
Debug.WriteLine( $"Start time for Test one : {DateTime.Now:T}" );
var sequence = Generate( 10, ( ) => DateTime.Now );
Debug.WriteLine( "Interating..." );
foreach ( var value in sequence )
Debug.WriteLine( $"{value:T}" );
Debug.WriteLine( "Waiting..." );
Thread.Sleep( 1000 );
Debug.WriteLine( "Interating..." );
foreach ( var value in sequence )
Debug.WriteLine( $"{value:T}" );
輸出:
Start time for Test one : 下午 01:27:06
Interating...
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
下午 01:27:06
Waiting...
Interating...
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
下午 01:27:07
由此例可得知,每一次呼叫 Generate 都將產生新的集合枚舉(由 Time Stamp 可看出);也就是說,兩次的訪問是互相獨立的,每一次的訪問都會即時產生新的元素。
另外值得注意的一點,query expressions 理論上可以訪問一個長度為無限大的集合枚舉。在設計 query 時,需留意是否可在組合訪問表達式時;早期的限制訪問範圍區間,避免效能上的浪費。 以下用一個簡單的例子演示。
public static IEnumerable<int> AllNumbers( )
{
var number = 0;
while ( number < int.MaxValue )
yield return number++;
}
寫法一:加入 Take 限制只回傳前 10 個元素。
var answers = from number in AllNumbers( )
select number;
var smallNumbers = answers.Take( 10 );
foreach ( var num in smallNumbers )
Debug.WriteLine( num.ToString( ) );
寫法二:利用 Where 繞行檢查整個集合枚舉。
ar answers = from number in AllNumbers( )
where number < 10
select number;
foreach ( var num in answers )
Debug.WriteLine( num.ToString( ) );
顯然寫法二會造成不必要的效能浪費,where 語句需繞行檢查整個集合;而因為原始集合已經排序(升冪排序),使用 Take 限制回傳元素數量會是比較好的選擇。
再來看一個例子。
var products = new List<Product>( );
// Order before filter.
var sortedProductsSlow = from p in products
orderby p.UnitInStock descending
where p.UnitInStock > 100
select p;
// Filter before order.
var sortedProductsFast = from p in products
where p.UnitInStock > 100
orderby p.UnitInStock descending
select p;
orderby 語句需要繞行整個集合枚舉,若是在 where 語句前執行,會對不必要的元素做排序;反之則能夠先經過 where 語句篩選,只排序需要的元素,大大減輕系統負擔。
1. Lazy Evaluation 可滿足大部分的情境。
2. Eager Evaluation 可利用呼叫 ToList( ) 或 ToArray( ) 立即取得集合;通常用於需要集合快照或是該集合需進行多次不同操作,減少取得資料的成本(e.g. SQL Database, 網路傳輸.)。