C#杂记之叁:LINQ
准备完了开题答辩,弄完了笔试,又咸鱼了数天、玩了数天《动物森友会》,一下子离上一篇C#杂记过了三个星期。
所以接下来要填的坑是LINQ和异步。弄完了这个,C#最大的几块拼图才得以完整,大抵能够叫是入了门。
¶查询表达式
所谓的LINQ,是语言集成查询Language INtegrated Query的缩写。通过引用命名空间System.Linq
,可以用可读性极强的、类似SQL查询语句形式的代码对一些数据结构,甚至直接对数据库进行便捷的操作,如查询、排序、分组等,而不必编写大量逻辑复杂的循环代码。LINQ的操作十分高深,这里我仅学习一些基础、常用的LINQ功能。
以下是一个典型的LINQ查询示例:
1 | static void Main(string[] args){ |
这段代码查询字符串数组name
中以S
开头的元素,收入queryResults
中,并将它们输出。其中类似SQL语句,包含“from…in…where…select”的部分被称为“查询表达式”。
from...in...
子句指定了要查询的数据,这里例子中是数组names
中的每一个元素n
。数据是指数据源中的元素,而LINQ支持的数据源必须支持IEnumerable<T>
接口,这是C#中所有的数据、集合都支持的。
where
子句不是必需的,它用表达式(也包括Lambda表达式)作为查询条件,针对每个n
,查询条件能返回一个bool值,以此限定查询结果的范围。
select
子句指定查询结果集中包含怎样的元素。对于这个例子,select
子句是其最简单的形式,即直接包含元素n
本身。更复杂的情况,比如可以用n.Length
来采集字符串的长度。
采集到的查询结果与数据源一样是实现了IEnumerable<T>
的集合,用foreach
语句就可以进行迭代。当然,也可以用IEnumerable<T>
接口的ToList<T>
方法将其创建成列表再做进一步处理。
实际上,直到访问查询结果之前,LINQ查询都并没有真的进行。查询结果变量只是保存了“执行查询的计划”,这被叫做查询的“延迟执行”。
除了上述的构造查询表达式之外,还可以用LINQ支持的一系列扩展方法实现类似的功能。比如在C#杂记之壹:委托与事件里我曾写到的,当引用System.Linq
命名空间后,IEnumerable<T>
接口中就会多出一些扩展方法,比如其中的Where()
方法,它接受一个Func<TSource, bool>
委托作为参数,用于筛选数据源中的元素,类似于查询表达式中的查询条件。这行代码里,用到两个Lambda表达式作为委托:
1 | var queryResults = numList.Where(i => i < 5).Select(i => i * 2); |
用了几次LINQ之后,我发现用扩展方法写反而更常见,甚至也更方便。
¶排序
在查询表达式中使用orderby
子句,可以对查询结果进行排序。该子句默认按照排序依据升序,如果在子句末尾使用descending
关键字就可改为降序。下面的例子里,orderby
子句依据字符串元素的最后一个字符,对查询结果进行降序排序:
1 | var queryResults = |
至于多级排序,形式也十分简单:
1 | //三级排序,最后是构造匿名类型,迭代访问时用var即可 |
¶聚合
有时我们并不关注每一条查询结果的细节,所要的只是关于查询结果的一个统计值,这就是所谓的聚合运算。既然学过SQL,这几个基本聚合运算符也是“老朋友”了:
运算符 | 说明 |
---|---|
Count() | 结果的个数 |
Min() | 结果中的最小值 |
Max() | 结果中的最大值 |
Average() | 数字结果的平均值 |
Sum() | 数字结果的总和 |
Distinct() | 结果去重(其实不是聚合) |
不过,LINQ与SQL仍有所不同,聚合运算符不是像SQL里一样写在select子句里,而是结果集所支持的方法。
SQL的形式:
1 | SELECT MAX(column_name) FROM table_name |
LINQ的形式,聚合运算时可以可选地传入委托作为元素的转换函数,然后对转换后的结果进行聚合:
1 | WriteLine(queryReusults.Max(i => i * 5 + 2)); |
¶分组查询
LINQ的分组查询类似于SQL的GROUP BY
,配合聚合函数使用得到分组聚合值,用group...by...into...
子句实现。
1 | var queryResults = |
上面这个例子,首先用group by into对数据源customers
进行分组,得到新的结果集cg
。通过分组查询得到的这个临时结果集实现了IGrouping
接口,它包含Key
属性,表示分组所依据的值,也就是by
后面所接的表达式(本例中是表示客户地区的c.Region
)之值。
最后的select
子句依然是构造匿名类型,这在需要投影多个属性时是很有用的。
¶join查询
和SQL中一样,join查询是用在需要用另外的数据源作为参考来进行查询,或者需要从多个数据源中汇集信息时的。SQL中实现多表查询至少有两种方法,一是使用JOIN,二是引用多个表然后把连接条件写在where子句中。但由于LINQ的from...in...
子句只能引用一个数据源,所以第二种方案是行不通的,需要使用join...in...on...
子句来实现。
1 | var queryResults = |
上面的例子是一个简单的内连接(Inner Join),其实LINQ也能实现外连接,只是不算那么优雅(而且好像也没多大卯月)。
¶交并差
这是LINQ为IEnumerable带来的非常贴心的功能,仅三个简单的扩展方法。
取并集:Union()
取交集:Intersect()
取差集:Except()
不过,由于数学上的“集合”是无重复的,上面的Union()方法返回的也是去重的结果。如果想保留重复的结果,可以使用Concat()。
这几个方法返回的类型都是IEnumerable,需要在调用之后再使用ToList()或者ToArray()之类的方法。
你甚至可以用这玩意一行代码秒掉LeetCode 349 两个数组的交集:
1 | public class Solution { |
¶总结
基础形式:from…in…where…select…
其中:
from…in…指定数据源是什么;
可选的where指定按照什么条件筛选;
select指定要获取怎样的结果;
排序:orderby… (descending)
聚合:Count(), Max(), Min(), Average(), Sum(), Distinct()
分组:group…by…into…
join:join…in…on…
明天真该async await了!(动森真好玩)