For,do… while,while ,foreach是大多數編程語言中常用的循環控制語句,在C#中查詢表達式也能實現同樣的效果。
查詢表達式使得編程風格從”命令式”變得更加的”聲明式”。查詢表達式定義想要的結果以及要達到該結果需要執行的方法,而不是具體的如何實現。這裡重點是查詢表達式,通過擴展方法,查詢表達式使得能夠比命令式循環構造更能夠清楚的表達意圖。
下面的語句顯示了一個命令式風格的填充一個數組並打印到控制台上:
int[] 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());
} 即使實現這麼一小點功能,依然注重的是如何實現而不是怎樣實現,將這段代碼改為查詢表達式能夠使得代碼更易讀和使用:int[] foo = (from n in Enumerable.Range(0, 100) select n * n).ToArray();第二個循環可以利用擴展方法改寫為:foo.ForAll(n => Console.WriteLine(n));或者更為簡潔的:
foo.ForAll(Console.WriteLine);
在.NET BCL中ForEach擴展方法有針對List<T>的實現方法。我們可以添加一個針對IEnumerable<T>的ForAll擴展方法實現如下:
public static class Extensions
{
public static void ForAll<T>(this IEnumerable<T> sequence, Action<T> action)
{
foreach (T item in sequence)
{
action(item);
}
}
} 可能看起來不起眼,只不過是一個擴展方法而已,但是這能夠使得代碼能夠更多的重用,任何時候要對一個序列元素執行操作,ForAll就能夠派上用場。
以上問題可能看上去比較簡單,不足以看出使用查詢語法所帶來的好處,下面來看個復雜一點的。
很多操作需要嵌套的循環操作,假設需要產生一個從0到99的(X,Y)對,通常的做法是:
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices()
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
yield return Tuple.Create(x, y);
}
}
}或者改為查詢表達式:
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices()
{
return from x in Enumerable.Range(1, 100)
from y in Enumerable.Range(1, 100)
select Tuple.Create(x, y);
}兩者看起來差不多,但是隨著問題的復雜,查詢表達式仍然能夠保持簡潔。現在將問題改為:只產生x和y相加小於100的點對,比較兩者的實現:
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices2()
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
if (x+y<100)
yield return Tuple.Create(x, y);
}
}
}
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices2()
{
return from x in Enumerable.Range(1, 100)
from y in Enumerable.Range(1, 100)
where x+y<100
select Tuple.Create(x, y);
}
差距仍不明顯,但是命令式語句開始隱藏我們想要的意圖。再將問題變得復雜一些。現在,我們需要將返回的點按照距離原點位置的距離降序排列。
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices3()
{
var storage = new List<Tuple<int, int>>();
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
if (x+y<100)
storage.Add(Tuple.Create(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;
}
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices3()
{
return from x in Enumerable.Range(1, 100)
from y in Enumerable.Range(1, 100)
where x + y < 100
orderby (x * x + y * y) descending
select Tuple.Create(x, y);
} 現在,差距開始出現了,命令式的風格時的代碼難以理解,如果不認真的看,很難一下子明白比較函數後面一堆東西返回的是什麼。如果沒有注釋的話,這段命令式代碼比較難懂。命令式風格的代碼過多的強調了代碼執行的過程,很容易在這復雜的過程中迷失我們最初想要達到的目的。
查詢表達式比循環控制結構更好的一點是:查詢表示式能夠更好的組合,他能夠將算法組織在一個很小的精簡的片段內來執行一系列的操作。查詢的惰性執行模型也使得開發者能夠在一個循環類執行一系列的操作。而查詢表達式則不能做到這一點。你必須存儲每一次循環的結果,然後構造新的循環操作來執行上一次的操作結果。
最後一個例子演示了如何工作。操作組合了過濾(where子句),排序(orderby 子句)以及投影(select語句),所有這些操作在一次遍歷操作中完成。而命令式版本的方法需要創建一個零時的存儲對象storage,然後對這個對象分別執行一些列操作。
每一個查詢表達式有一個對應的方法調用是的表達式,有時候,查詢表達式更自然有時候方法調用是的表達式更自然。顯然在上面的例子中,查詢表達式版本更易讀,下面是方法調用的版本:
public static IEnumerable<Tuple<Int32, Int32>> ProduceIndices3()
{
return Enumerable.Range(1, 100).
SelectMany(x => Enumerable.Range(1, 100),
(x, y) => Tuple.Create(x, y)).Where(pt => pt.Item1 + pt.Item2 < 100).
OrderByDescending(pt => pt.Item1 * pt.Item1 + pt.Item2 * pt.Item2);
}
在這個例子中,查詢表達式可能比方法表達式更易讀,但其他例子可能不同。有些方法表達式並沒有對應的查詢表達式。一些方法,如Take,TakeWhile,Skip,SkipWhile,Min,以及Max在一些情況下需要使用方法表達式。
有些人可能會人會查詢表達式在執行速度上比循環執慢,這一點要看具體的情況。有時候清晰的代碼可能比速度更重要。在改進程序算法之前,可以考慮LINQ的並行擴展庫PLINQ,可以使用.AsParallel()來使的查詢操作能夠並行執行。
C#最開始是一種命令式編程語言,你能夠使用之前最熟悉的方法去編寫代碼。但是這些傳統的方法可能不是最好的。當你發現需要使用循環結構去執行某種操作的時候,試試能夠將它改寫為查詢表達式的方式,如果查詢表達式不起作用,試試方法表達式,在大多數情況下,你會發現使用查詢表達式或者方法表達式會比使用傳統的循環式的命令式結構能使得代碼變得更加簡潔和清晰。