C#语言中并不缺少控制程序流程的结构,for、while、do/while和foreach
等都可以做到这一点。但我们还有更好的方式:查询语法(query syntax)
下面这段代码演示了用命令式的方式填充一个数组,然后将其内容输出到控制台:
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.ToString()));
//扩展
public static class Extensions
{
public static void ForAll<T>(this IEnumerable<T> sequence, Action<T> action)
{
foreach(T item in sequence)
action(item);
}
}
这只是个简单的操作,因此你可能看不到太多的好处。确实如此,不过你可以继续看看其他一些问题。
很多操作要处理嵌套循环。例如,用0~99的整数生成所有的(X, Y)二元数组,使用嵌套循环也不难:
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
for(int x = 0; x < 100; x++)
for(int y = 0; y < 100;y++)
yield return Tuple.Create(x, y);
}
当然也可以用查询来生成同样的数据:
private static IEnumerable<Tuple<int, int>> QueryIndices()
{
return from x in Enumerable.Range(0, 100)
from y in Enumerable.Range(0, 100)
select Tuple.Create(x, y);
}
看上去还是差不多,我们继续更改一下问题,我们需要返回的二元组按照其离远点距离的逆序排列。
下面两个不同的方法能生成同样正确的结果:
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
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
));
}
private static IEnumerable<Tuple<int, int>> QueryIndices()
{
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 Tuple.Create(x, y);
}
现在就应该感觉到明显不同了。相比而言,命令式的版本非常难以理解,要是没有注释或文档,后续维护人员将要重读整段代码才能进行开发。
另外,查询语法比循环结构能提供更具组合性的API。查询语法将很自然的把代码分解成小块代码,每一块仅仅对序列中元素进行单一的操作。查询语法的延迟执行模型也让开发者能将这些单一的操作组合成多步操作,且在一次遍历序列时完整执行。
于此同时,方法调用也能同样实现:
private static IEnumerable<Tuple<int, int>> MethodIndices()
{
return Enumerable.Range(0, 100)
.SelectMany(x => Enumerable.Range(0, 100).(x, y) => Tuple.Create(x, y))
.Where(pt => pt.Item1 + pt.Item2 < 100)
.OrderByDescending(pt => pt.Item1 * pt.Item1 + pt.Item2 * pt.Item2);
}
综上所述,当你编写循环时,首先看看能不能用查询语法实现。若是无法使用查询语法,再看看是否可以使用方法调用语法替代。