C#杂记之叁:LINQ

准备完了开题答辩,弄完了笔试,又咸鱼了数天、玩了数天《动物森友会》,一下子离上一篇C#杂记过了三个星期。

朔月

所以接下来要填的坑是LINQ和异步。弄完了这个,C#最大的几块拼图才得以完整,大抵能够叫是入了门。

查询表达式

所谓的LINQ,是语言集成查询Language INtegrated Query的缩写。通过引用命名空间System.Linq,可以用可读性极强的、类似SQL查询语句形式的代码对一些数据结构,甚至直接对数据库进行便捷的操作,如查询、排序、分组等,而不必编写大量逻辑复杂的循环代码。LINQ的操作十分高深,这里我仅学习一些基础、常用的LINQ功能。

以下是一个典型的LINQ查询示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void Main(string[] args){
//数据源
string[] name = {"Alonso", "Zheng", "Smith", "Jones", "Smythe", "Small", "Ruiz", "Hsieh", "Jorgenson", "Ilyich", "Singh", "Samba", "Fatimah" };

//查询表达式
var queryResults =
from n in names
where n.StartWith("S")
select n;

//迭代查询结果
WriteLine("Name beginning with S:");
foreach(var item in queryResults){
WriteLine(item);
}

Write("Program finished. Press Enter/Return to continue:");
ReadLine();

}

这段代码查询字符串数组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
2
3
4
5
6
7
var queryResults = numList.Where(i => i < 5).Select(i => i * 2);

//上面的方法语法等同于以下的查询表达式:
var queryResults =
from i in numList
where i < 5
select i * 2;

用了几次LINQ之后,我发现用扩展方法写反而更常见,甚至也更方便。

排序

在查询表达式中使用orderby子句,可以对查询结果进行排序。该子句默认按照排序依据升序,如果在子句末尾使用descending关键字就可改为降序。下面的例子里,orderby子句依据字符串元素的最后一个字符,对查询结果进行降序排序:

1
2
3
4
5
var queryResults = 
from n in names
where n.StartWith("S")
orderby n.Substring(n.length-1) descending
select n ;

至于多级排序,形式也十分简单:

1
2
3
4
5
//三级排序,最后是构造匿名类型,迭代访问时用var即可
var queryResults =
from c in cunstomers
orderby c.Region, c.Country descending, c.City
select new {c.ID, c.Region, c.Country, c.City};

聚合

有时我们并不关注每一条查询结果的细节,所要的只是关于查询结果的一个统计值,这就是所谓的聚合运算。既然学过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
2
3
4
var queryResults = 
from c in customers
group c by c.Region into cg
select new { TotalSales = cg.Sum(c => c.sales), Region = cg.Key };

上面这个例子,首先用group by into对数据源customers进行分组,得到新的结果集cg。通过分组查询得到的这个临时结果集实现了IGrouping接口,它包含Key属性,表示分组所依据的值,也就是by后面所接的表达式(本例中是表示客户地区的c.Region)之值。

最后的select子句依然是构造匿名类型,这在需要投影多个属性时是很有用的。

join查询

和SQL中一样,join查询是用在需要用另外的数据源作为参考来进行查询,或者需要从多个数据源中汇集信息时的。SQL中实现多表查询至少有两种方法,一是使用JOIN,二是引用多个表然后把连接条件写在where子句中。但由于LINQ的from...in...子句只能引用一个数据源,所以第二种方案是行不通的,需要使用join...in...on...子句来实现。

1
2
3
4
5
6
7
8
9
10
var queryResults = 
from c in customers
join o in orders on c.ID equals o.ID
select new {
c.ID,
c.City,
SalesBefore = c.Sales,
NewOrder = o.Amount,
SalesAfter = c.Sales + o.Amount
};

上面的例子是一个简单的内连接(Inner Join),其实LINQ也能实现外连接,只是不算那么优雅(而且好像也没多大卯月)。

交并差

这是LINQ为IEnumerable带来的非常贴心的功能,仅三个简单的扩展方法。

取并集:Union()

取交集:Intersect()

取差集:Except()

不过,由于数学上的“集合”是无重复的,上面的Union()方法返回的也是去重的结果。如果想保留重复的结果,可以使用Concat()。

这几个方法返回的类型都是IEnumerable,需要在调用之后再使用ToList()或者ToArray()之类的方法。

你甚至可以用这玩意一行代码秒掉LeetCode 349 两个数组的交集

1
2
3
4
5
public class Solution {
public int[] Intersection(int[] nums1, int[] nums2) {
return nums1.Intersect(nums2).ToArray(); //LINQ完事,别想算法了
}
}

总结

基础形式: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了!(动森真好玩)

博物馆庆典