查詢文法(query syntax)可以讓程式邏輯的表達由“命令式”轉換為“聲明式”。查詢文法定義了想要的結果,而把具體實現交給其他的專門實現。使用查詢文法(實現了查詢運算式模式的方法文法也可以)要比傳統的命令式迴圈結果更加清晰的表達你的意圖。
下面我們觀察一個使用命令式方法填充一個數組,然後將其內容輸出至控制台:
1 static void Main(string[] args) 2 { 3 int[] foo = new int[100]; 4 for (int num = 0; num < foo.Length; num++) 5 { 6 foo[num] = num * num; 7 } 8 foreach (int i in foo) 9 {10 Console.WriteLine(i);11 }12 Console.Read();13 }
編寫命令式的代碼需要關注具體的實現細節。但是如果採用查詢文法,實現同樣的功能,代碼更加易於重用且易讀,我們來看上面樣本“聲明式”的寫法:
1 static void Main(string[] args) 2 { 3 //產生數組的工作交個一個查詢完成 4 int[] foo = (from n in Enumerable.Range(0, 100) select n * n).ToArray(); 5 6 //迴圈列印的工作交給一個數組的擴充方法來完成 7 foo.ForAll((n) => Console.WriteLine(n.ToString())); 8 9 Console.Read();10 }
我們看到在負責迴圈列印部分我們使用了一個擴充方法,這個擴充方法帶來了更好的重用性,每次需要對一個序列的元素執行某個操作都可以使用ForAll()方法:
1 public static class Extensions 2 { 3 /// <summary> 4 /// 為IEnumerable<T>類型添加擴充方法 5 /// </summary> 6 /// <typeparam name="T"></typeparam> 7 /// <param name="sequence"></param> 8 /// <param name="action"></param> 9 public static void ForAll<T>(this IEnumerable<T> sequence, Action<T> action)10 {11 foreach (T item in sequence)12 {13 action(item);14 }15 }16 }
上面的執行個體比較簡單,似乎看不出二者有多大的區別,在下面的執行個體中我們分別使用“命令式”和“聲明式”來實現比較二者的區別 。
命令式:
View Code
1 private static IEnumerable<Tuple<int, int>> ProduceIndices() 2 { 3 #region 用0到99的整數生產所以的(X,Y)二元組 4 5 //for (int x = 0; x < 100; x++) 6 // for (int y = 0; y < 100; y++) 7 // yield return Tuple.Create(x, y); 8 9 #endregion10 11 #region X和Y的和要小於10012 13 //for (int x = 0; x < 100; x++)14 // for (int y = 0; y < 100; y++)15 // if (x + y < 100)16 // yield return Tuple.Create(x, y);17 18 #endregion19 20 #region 二元組按照其離遠點的距離逆序排列21 22 var storage = new List<Tuple<int, int>>();23 24 for (int x = 0; x < 100; x++)25 for (int y = 0; y < 100; y++)26 if (x + y < 100)27 storage.Add(Tuple.Create(x, y));28 29 storage.Sort((point1, point2) => (point1.Item1 * point2.Item1 + point2.Item2 * point2.Item2).CompareTo(point1.Item1 * point1.Item1 + point1.Item2 * point1.Item2));30 return storage;31 32 #endregion33 }
聲明式:
View Code
1 private static IEnumerable<Tuple<int, int>> QueryIndices() 2 { 3 #region 用0到99的整數生產所以的(X,Y)二元組 4 5 //return from x in Enumerable.Range(0, 100) 6 // from y in Enumerable.Range(0, 100) 7 // select Tuple.Create(x, y); 8 9 #endregion10 11 #region X和Y的和要小於10012 13 //return from x in Enumerable.Range(0, 100)14 // from y in Enumerable.Range(0, 100)15 // where x + y < 10016 // select Tuple.Create(x, y);17 18 #endregion19 20 #region 二元組按照其離遠點的距離逆序排列21 22 return from x in Enumerable.Range(0, 100)23 from y in Enumerable.Range(0, 100)24 where x + y < 10025 orderby (x * x + y * y) descending26 select Tuple.Create(x, y);27 28 #endregion29 }
我們可以看到隨著編程任務的複雜:
“命令式”版本變得越來越難以理解。如果仔細看的話,甚至都不會發現比較函數中參數被顛倒了(這是個錯誤),而這隻是為了能夠降序排列而已。要是沒有任何注釋和穩定,命令式代碼將會更加難以閱讀。”命令式“代碼太過於強調實現目標所需要的詳細步驟,以至於讓人很容易陷入具體的細節中。
“聲明式”版本的最後一個實現,實際只是將以此過濾(where子句)、以此排序(orderby子句)和一個投射(select)組合起來。查詢文法比迴圈結構能夠提供更具組合性的API。查詢文法很自然的將演算法分解成小塊代碼,每一塊代碼靜對序列中的元素進行單一操作。查詢文法的順延強制模式也讓開發人員能夠將這些單一的操作組合成多步的操作,且只要一次遍曆序列就可以完整執行,而迴圈文法結構則必須為每一步操作都建立臨時的儲存,或者為序列將要執行的每一批操作都建立專用的方法。
小節:
當你需要編寫迴圈時,首先看看能否用查詢文法實現,若是無法使用查詢文法,那麼再看看是否能以方法調用文法替代。這樣寫出的代碼總會比命令式迴圈結構要簡潔一些。