《Effective C#》读书笔记——条目8:推荐使用查询语法而不是循环<C#语言习惯>
查询语法(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 }
上面的实例比较简单,似乎看不出二者有多大的区别,在下面的实例中我们分别使用“命令式”和“声明式”来实现比较二者的区别 。
命令式:
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 #endregion 10 11 #region X和Y的和要小于100 12 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 #endregion 19 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 #endregion 33 }
声明式:
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 #endregion 10 11 #region X和Y的和要小于100 12 13 //return from x in Enumerable.Range(0, 100) 14 // from y in Enumerable.Range(0, 100) 15 // where x + y < 100 16 // select Tuple.Create(x, y); 17 18 #endregion 19 20 #region 二元组按照其离远点的距离逆序排列 21 22 return from x in Enumerable.Range(0, 100) 23 from y in Enumerable.Range(0, 100) 24 where x + y < 100 25 orderby (x * x + y * y) descending 26 select Tuple.Create(x, y); 27 28 #endregion 29 }
我们可以看到随着编程任务的复杂:
“命令式”版本变得越来越难以理解。如果仔细看的话,甚至都不会发现比较函数中参数被颠倒了(这是个错误),而这只是为了能够降序排列而已。要是没有任何注释和稳定,命令式代码将会更加难以阅读。”命令式“代码太过于强调实现目标所需要的详细步骤,以至于让人很容易陷入具体的细节中。
“声明式”版本的最后一个实现,实际只是将以此过滤(where子句)、以此排序(orderby子句)和一个投射(select)组合起来。查询语法比循环结构能够提供更具组合性的API。查询语法很自然的将算法分解成小块代码,每一块代码静对序列中的元素进行单一操作。查询语法的延迟执行模式也让开发者能够将这些单一的操作组合成多步的操作,且只要一次遍历序列就可以完整执行,而循环语法结构则必须为每一步操作都创建临时的存储,或者为序列将要执行的每一批操作都创建专用的方法。
小节:
当你需要编写循环时,首先看看能否用查询语法实现,若是无法使用查询语法,那么再看看是否能以方法调用语法替代。这样写出的代码总会比命令式循环结构要简洁一些。