Effective C# (Covers C# 6.0), (includes Content Update Program): 50 Specific Ways to Improve Your C#, 3rd Edition By Bill Wagner 讀後心得
Linq 加入 C# 特性後,繞行查詢有以下三種方式。
1. 傳統的 for, while, do/while, foreach 表達式。
2. query 語法。
3. Method Call 擴充方法。
Note:實務上應盡量採用 query 方式,讓意圖更加明確;而 Method Call 的方式只有在 query 無法滿足需求時採用(Take, TakeWhile, Skip, SkipWhile, Min, Max, etc.)。
範例:
var foo = new int[ 100 ];
for ( int num = 0; num < foo.Length; num++ )
foo[ num ] = num * num;
foreach ( int i in foo )
Console.WriteLine( i.ToString( ) );
這是一個很簡單的例子,試著將其用 query 的方式改寫。
var foo = ( from n in Enumerable.Range( 0, 100 )
select n * n ).ToArray( );
foo.forAll( n => Console.WriteLine( n.ToString( ) ) );
public static void forAll<T>( this IEnumerable<T> sequence,
Action<T> action )
{
foreach ( T item in sequence )
action( item );
}
如此寫法是否有比較容易閱讀呢?
接著考慮比較複雜的情境。
private static IEnumerable<ValueTuple<int, int>> produceIndices( )
{
for ( int x = 0; x < 100; x++ )
for ( int y = 0; y < 100; y++ )
yield return (x, y);
}
用巢狀的迴圈開始讓方法變得複雜且漸漸難以閱讀。
同樣的,用 query 的方式改寫。
private static IEnumerable<ValueTuple<int, int>> queryIndices( )
{
return from x in Enumerable.Range( 0, 100 )
from y in Enumerable.Range( 0, 100 )
select (x, y);
}
這樣可能還沒甚麼感覺,把範例再變的複雜一點。
private static IEnumerable<ValueTuple<int, int>> produceIndices2( )
{
for ( int x = 0; x < 100; x++ )
for ( int y = 0; y < 100; y++ )
if ( x + y < 100 )
yield return (x, y);
}
兩層的巢狀迴圈加上最內層的 if 判斷式,難以一眼看出意圖。
接著用 query 的方式改寫。
private static IEnumerable<ValueTuple<int, int>> queryIndices2( )
{
return from x in Enumerable.Range( 0, 100 )
from y in Enumerable.Range( 0, 100 )
where x + y < 100
select (x, y);
}
加入了 where 語法,一眼即可看出意圖且較為簡潔。
目前為止,使用巢狀迴圈或 query 語法皆沒有太大的差異,即便巢狀迴圈較難以一眼看出意圖,但尚可接受。接下來的範例會拉開兩者可讀性的差異。
private static IEnumerable<ValueTuple<int, int>> produceIndices3( )
{
var storage = new List<ValueTuple<int, int>>( );
for ( int x = 0; x < 100; x++ )
for ( int y = 0; y < 100; y++ )
if ( x + y < 100 )
storage.Add( (x, y) );
storage.Sort( ( point1, point2 ) =>
( point2.Item1 * point2.Item1 + point2.Item2 * point2.Item2 ).CompareTo(
point1.Item1 * point1.Item1 + point1.Item2 * point1.Item2 ) );
return storage;
}
首先方法內宣告了一個 storage 儲存結果,接著呼叫排序方法;如果沒特別注意,我們很難發現 Sort 方法內的 CompareTo 是反向的。
用 query 的方式改寫,觀察會不會更容易理解。
private static IEnumerable<ValueTuple<int, int>> queryIndices3( )
{
return from x in Enumerable.Range( 0, 100 )
from y in Enumerable.Range( 0, 100 )
where x + y < 100
orderby ( x * x + y * y ) descending
select (x, y);
}
query 寫法的優勢已完全展現,語意清楚明白且沒有多餘的程式碼。
最後,query 寫法也可以寫成 Method Call 擴充方法。
private static IEnumerable<ValueTuple<int, int>> methodIndices3( )
{
return Enumerable.Range( 0, 100 ).
SelectMany( x => Enumerable.Range( 0, 100 ),
( x, y ) => (x, y) ).
Where( pt => pt.Item1 + pt.Item2 < 100 ).
OrderByDescending( pt =>
pt.Item1 * pt.Item1 + pt.Item2 * pt.Item2 );
}
Method Call 在語意上並沒有比 query 簡潔好懂,除了特殊情況應用(如 Note 所提及),應盡量使用 query。
結論:
1. 使用 query 方式寫出語意清晰的程式碼。
2. 只有在 query 無法滿足需求時,才考慮使用 Method Call 擴充。
1. 使用 query 方式寫出語意清晰的程式碼。
2. 只有在 query 無法滿足需求時,才考慮使用 Method Call 擴充。