1-3 - C#语言习惯 - 推荐使用查询语法而不是循环
C#语言中并不缺少控制程序流程的结构,for、while、do-while和foreach等都可以做到这点。
历史上所有计算机语言设计者都不曾遗漏这些重要的循环控制结构。
不过我们还有一个更好的方式:查询与法(query syntax)。
查询语法可以让程逻辑的表达式由“命令式”转为“声明式”。
查询语法定义了想要的结果,而把如何得到这些结果的任务交给了其他的专门实现。
本篇提到的所有查询语法都可以通过方法调用语法来实现,并感受其带来的所有好处。
不过最重要的是,查询语法(实现了查询语法表达式模式的方法语法也可以)要比传统的命令式循环结构更加清晰地表达你的意图。
下面这一段代码演示了用命令的方式填充一个数组,然后将其内容输出至控制台:
];
; i < foo.Length; i++)
{
foo[i] = i * i;
}
foreach (int item in foo)
{
Console.WriteLine(item.ToString());
}
即使是编写这一段简单的代码,你也需要关注于具体的实现细节。
而若是采用查询语法实现同样的功能,那么代码将变得更加易读且易于重用:
第一步,可以将生成数组的工作交给一个查询完成:
, ) select n * n).ToArray()
类似的修改可以应用到第二个循环上,不过这里你要编写一个扩展方法来逐一操作集合中的元素:
foo.ForAll((n) => Console.WriteLine(n.ToString()));
但是,int类型数组并没有提供ForAll()的查询方法。但是.NET BCL已经为List<T>提供了一个ForAll的实现。
因此,我们只需要为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)二元组,使用嵌套循环也不难:
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
; x < ; x++)
{
; y < ; y++)
{
yield return Tuple.Create(x, y);
}
}
}
当然,你也可以使用查询来生成同样的数据:
private static IEnumerable<Tuple<int, int>> QueryIndices()
{
return
, )
, )
select Tuple.Create(x, y);
}
二者看上去有些相似,不过即使问题变得越来越复杂,查询语法一样可以保持简单。
比如说,我们规定要生成的(x,y)二元组中x和y的和要小于100,那么两个方法就要变成:
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
; x < ; x++)
{
; y < ; y++)
{
)
{
yield return Tuple.Create(x, y);
}
}
}
}
private static IEnumerable<Tuple<int, int>> QueryIndices()
{
return
, )
, )
)
select Tuple.Create(x, y);
}
看上去仍然差不多,不过命令式的语法已经开始将要表达的语意逐渐隐藏在必要的语法中了。
我们继续更改一下问题,我们需要让返回的二元组按照其远离远点的距离逆序排列。
下面两个不同的方法能生成同样的正确结果:
private static IEnumerable<Tuple<int, int>> ProduceIndices()
{
var storage = new List<Tuple<int, int>>();
; x < ; x++)
{
; y < ; y++)
{
)
{
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;
}
private static IEnumerable<Tuple<int, int>> QueryIndices()
{
return
, )
, )
)
orderby (x * x + y * y) descending
select Tuple.Create(x, y);
}
运行结果如下:

现在,你可以看到明显的不同了吧。相比而言,命令式的方法非常难以理解。
如果你不仔细看的话,甚至都不会发现比较函数中参数被颠倒了(实际上这是个错误),而这只是为了能够降序排列自己。
要是没有任何注释或文档,命令式的代码将会更加难以阅读并理解。
即使你足够细心地发现了比较函数中参数的颠倒,你是不是会觉得这是个错误呢?
这一段命令式代码太过于强调实现目标所需要的详细步骤,以至于让人很容易陷入此类细节,甚至忘记了最初将要达到的目的是什么。
还有一个让你更加倾向于使用查询语法的理由:
查询语法比循环结构能提供更具有组合性的API。查询语法将很自然地把算法分解成小块的代码,每一块仅仅对序列中的元素进行单一的操作。
查询语法的延迟执行模型也让开发者能将这些单一的操作组合成多步的操作,且在一次遍历序列的时候就可以完整执行,而循环结构则无法以类似的方式组合起来。
你必须为每一步操作创建临时的存储,或者为序列将要执行的每一批操作都创建专用的方法。
最后一个例子说明了上述工作原理,其操作实际上是将一次过滤(where)子句、一次排序(orderby)子句和一个查询(select)子句组合了起来。
所有的这些步骤都仅在一次遍历中实现。命令式的代码则创建了一个中间的存储,将排序操作分离开来。
虽然叫做“查询语法”,不过每一个查询都有一个与之对应的方法调用语法。
有些时候使用查询比较自然,而有些时候方法调用却更加直观一些。
对于上面的例子,查询语法的可读性更强。这个查询对应的方法调用语法如下:
private static IEnumerable<Tuple<int, int>> MethodIndices3()
{
return
Enumerable.Range(, ).
SelectMany(x => Enumerable.Range(, ), (x, y) => Tuple.Create(x, y)).
Where(pt => pt.Item1 + pt.Item2 < ).
OrderByDescending(pt => pt.Item1 * pt.Item1 + pt.Item2 * pt.Item2);
}
到底查询语法还是方法调用语法更可读一些,这是个仁者见仁的问题。
不过对于这个例子,我相信查询语法更清晰一些。但其它的一些例子,情况也许有些不同。
此外,有些方法没有与之对应的查询语法,例如Take、TakeWhile、Skip、SkipWhile、Min、Max等。
如果需要使用这些方法,那么就必须要以方法调用语法完成。
不过其它的一些语言,例如VB.NET,却为其中的很多方法提供了对应的查询语法关键词。
但有些人会经常提起,查询语法执行速度要逊于普通循环。
然而,虽然你可以容易地设计出一个手工编写的、比查询语法高性能的循环,但这并不是问题的关键。
你首先需要判断的是,是不是某个特别的情况下某个查询的效率有问题。
在你最终决定放弃查询语法之前,还可以试一下LINQ的并行计算扩展。只需要简单地在查询后添加.AsParallel()方法即可。
C#起初是以命令式语言的方式设计的,随后越走越远,组件提供了命令式语言的大多数功能。
人们会很自然地选择最熟悉的工具,不过这并不带包这些工具就是最高效的。
当你需要编写循环时,首先看看能否用查询语法实现。若是无法使用查询语法,那么再看看是否可以使用方法调用语法替代。
你会发现,这样写出的代码总会比命令式循环结构要简洁一些。
1-3 - C#语言习惯 - 推荐使用查询语法而不是循环的更多相关文章
- 《C#高效编程》读书笔记08-推荐使用查询语法而不是循环
C#语言中并不缺少控制程序流程的结构,for.while.do/while和foreach等都可以做到这一点.但我们还有更好的方式:查询语法(query syntax) 下面这段代码演示了用命令式的方 ...
- [.NET] 《C# 高效编程》(一) - C# 语言习惯
C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Con ...
- [.NET] 《Effective C#》快速笔记(一)- C# 语言习惯
<Effective C#>快速笔记(一)- C# 语言习惯 目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 ...
- 《Effective C#》快速笔记(一)- C# 语言习惯
目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional ...
- C# 语言习惯
目录 一.使用属性而不是可访问的数据成员 二.使用运行时常量(readonly)而不是编译时常量(const) 三.推荐使用 is 或 as 操作符而不是强制类型转换 四.使用 Conditional ...
- 2008技术内幕:T-SQL语言基础 单表查询摘记
这里的摘抄来自<Microsoft SQL Server 2008技术内幕:T-SQL语言基础>,书中用到的案例数据库是这个 TSQLFundamentals2008 ,官网给出的连接是这 ...
- 改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员(整理中)
改善 C# 的语言习惯(一) - 使用属性而不是可访问的数据成员 序 为什么我们的程序运行得棒棒的,还要改呢?Why? 答:我们要让程序运行得更快,执行的效率更高,代码的可读性更强,维护的成本更低.. ...
- Entity Framework 基于方法的查询语法
实体框架(Entity Framework )是 ADO.NET 中的一套支持开发面向数据的软件应用程序的技术. LINQ to Entities 提供语言集成查询 (LINQ) 支持,它允许开发 ...
- LINQ to Entities 查询语法
转自: http://www.cnblogs.com/asingna/archive/2013/01/28/2879595.html 实体框架(Entity Framework )是 ADO.NET ...
随机推荐
- Java垃圾回收
垃圾收集算法 引用计数 堆中的每个对象都有一个引用计数,当对象被引用时引用计数加1,当对象的引用被重新赋值或超出有效区域时引用计数减1,当一个对象被回收后,它所引用的对象的引用计算减1.当一个对象的引 ...
- 配置文件Java读写
今天把配置文件的Bug修复了,总结一下Java配置文件如何读写 配置文件的格式 以.properties后缀结尾,内容不出现空格和双引号 //config.properties Driver=com. ...
- DAO层,Service层,Controller层、View层 的分工合作
DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口 ...
- 同步与异步 & 阻塞与非阻塞
在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式: 一.同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用 ...
- 解析大型.NET ERP系统 单据标准(新增,修改,删除,复制,打印)功能程序设计
ERP系统的单据具备标准的功能,这里的单据可翻译为Bill,Document,Entry,具备相似的工具条操作界面.通过设计可复用的基类,子类只需要继承基类窗体即可完成单据功能的程序设计.先看标准的销 ...
- 【.net深呼吸】动态类型(高级篇)
前面老周给大家介绍了动态类型使用的娱乐级别用法,其实,在很多情景下,娱乐级别的用法已经满足需求了. 如果,你想自己来控制动态类型的行为和数据的存取,那么,就可以考虑用今天所说的高大上技术了.比如,你希 ...
- Hybrid App技术批量制作APP应用与跨平台解决方案
前言 简单的聊一聊我开发了4年之久的Hybrid App(混合模式移动应用)平台开发,目前一直在持续开发与维护,支持无编程快速开发! 其本意也不是要吹捧前端有多么强大,只是用自己的实际项目阐述下对于前 ...
- 前端学HTTP之数据传输
× 目录 [1]客户机处理 [2]集线器处理 [3]路由器1处理[4]路由器2处理[5]交换机处理[6]服务器处理[7]反向传输 前面的话 上一篇中,介绍了网络基础.本文将详细介绍客户机在浏览网页ab ...
- 【NLP】条件随机场知识扩展延伸(五)
条件随机场知识扩展延伸 作者:白宁超 2016年8月3日19:47:55 [摘要]:条件随机场用于序列标注,数据分割等自然语言处理中,表现出很好的效果.在中文分词.中文人名识别和歧义消解等任务中都有应 ...
- Vertica集群扩容实验过程记录
需求: 将3个节点的Vertica集群扩容,额外增加3个节点,即扩展到6个节点的Vertica集群. 实验环境: RHEL 6.5 + Vertica 7.2.2-2 步骤: 1.三节点Vertica ...