C#的语言集成查询LINQ
LINQ是一种很方便的查询工具,是一系列直接将查询功能集成到C#语言的技术统称。开发中常用的查询如果使用LINQ编写,能够大幅度节省代码,但性能上会略有影响,本编博客记录LINQ在开发场景下的主要应用。
扩展方法
了解LINQ语句之前首先解释如何实现,C#通过扩展方法在不新建派生类型的情况下为现有类型添加方法。LINQ是最常见的扩展方法,将查询功能添加到System.Collections.IEnumerable和System.Collections.Generic.IEnumerable<T>中,这些查询方法通常使用Lamda表达式作为参数。
扩展方法形式上通过非嵌套、非泛型的静态类内静态方法实现,例如下面是对string类型定义的扩展方法:
1 | namespace ExtensionMethods |
第一个参数需要用this修饰,后面是需要扩展的类型和名称,虽然是静态方法,但是可以通过实例进行调用,编译器的中间语言会将代码转换为对该静态方法的调用。
LINQ的查询操作
LINQ的查询操作可以通过三个步骤完成:
- 获取数据源
- 创建查询
- 执行查询
其中,数据源是支持IEnumrable\
子句 | 说明 |
---|---|
from | 指定数据源和范围变量(类似于迭代变量)。 |
where | 基于由逻辑 AND 和 OR 运算符分隔的一个或多个布尔表达式筛选源元素。 |
select | 指定执行查询时,所返回序列中元素的类型和形状。 |
group | 根据指定的密钥值对查询结果分组。 |
into | 提供可作为对 join、group 或 select 子句结果引用的标识符。 |
orderby | 根据元素类型的默认比较器对查询结果进行升序或降序排序。 |
join | 基于两个指定匹配条件间的相等比较而联接两个数据源。 |
let | 引入范围变量,在查询表达式中存储子表达式结果。 |
in | join 子句中的上下文关键字。 |
on | join 子句中的上下文关键字。 |
equals | join 子句中的上下文关键字。 |
by | group 子句中的上下文关键字。 |
ascending | orderby 子句中的上下文关键字。 |
descending | orderby 子句中的上下文关键字。 |
最主要的是from、where、select三个子句,from指定数据源,where指定筛选器,select指定返回的元素类型,举例如下:
1 | // Program.cs |
上述代码将suits中的s和ranks中的r一一配对,生成含有52个元素的IEnumerable类型。查询本身不会做任何数据操作,而是延迟执行在foreach对查询变量枚举时,因此每次对延迟执行的数据源查询都可能会得到不同结果,如果需要强制执行可以使用ToList或者ToArray方法。因此和普通的语法查询相比,使用LINQ需要注意对查询变量及时保存,除非确有延迟查询的需要。
自定义查询扩展方法
LINQ的查询不满足对自定义类型的查询要求,需要重新定义扩展方法。继续上面代码的例子,现在我们对这副扑克牌进行洗牌操作,也就是52张牌分成上下两半,然后交替重叠到一起。
1 | public static IEnumerable<T> InterleaveSequenceWith<T> |
由于是迭代器方法,所以可以使用yield return来返回单个元素。最后在Main方法中调用这个洗牌方法:
1 | // Program.cs |
如果多次执行洗牌操作,会发现LINQ性能上的问题,我们看下面的代码:
1 | // Program.cs |
由于延迟查询,每次洗牌过程中首先Take方法和Skip方法各对纸牌进行了查询,然后foreach处又需要查询一次,那么一次洗牌实际上花费三次LINQ查询。上面的代码统计几次洗牌后牌序会和原本一致,那么每执行一次洗牌,都会因为LINQ语句在shuffle被引用的时候进行多余查询,导致性能严重下降。
使用举例
下面代码使用LINQ的扩展方法ToDictionary将字典的value全部设置为false,字典的key是一个自定义的枚举类型。
1 | qualityChooseStateDic = qualityChooseStateDic.ToDictionary(pair => pair.Key, pair => true); |
该扩展方法将IEnumrable类型转换为字典,接受两个lamda表达式作为参数,实际上类型是Func<Tsource, Tkey>和Func<Tsource, Tvalue>。使用这类方法可以节省代码,不然对字典的赋值需要编写循环方法。