Linq表示语言集成查询,它是.Net框架的扩展,允许我们以使用sql查询数据库的类似方法来查询集合。
Linq的查询语法有方法语法和查询语法。方法语法是命令式的,查询语法是声明式的。
位于System.Linq名称空间。
Linq查询可以返回两种类型的结果——可以是一个枚举,它是满足查询参数的项列表;也可以是一个叫标量的单一值,它是满足查询条件的结果的某种摘要形式。

匿名类型

学习Linq之前,让我们先来认识一下匿名类型。

1
2
3
4
5
var student = new {
age = 18,
name = "张三",
major = "数字电路"
};

匿名类型的成员类型由编译器推断,只需要在对象初始化语句中初始化即可。
注意事项:
匿名类型只能用于局部变量,不能用于类成员。
必须使用var作为变量类型。
编译器为匿名类型创建的属性是只读的。
可以使用投影初始化语句来初始化匿名对象(与JS一样):

1
2
3
4
5
6
7
8
static void Main(string[] args) {
int age = 18;
string name = "张三";
string major = "数字电路";
var student = new {
age, name, major
};
}

如果编译器遇到了另一个具有相同参数名,相同的推断类型和相同顺序的匿名类型对象初始化语句,它会重用这个类型并直接创建新的实例,不会创建新的匿名类型。

查询语法(微软推荐,我不推荐)

1
2
3
4
5
6
7
List<int> list = new List<int>() { 1,2,3,4,5,6,7,8,9,10};
var result = from n in list
where n>5
select n;
foreach(var n in result) {
Console.WriteLine(n);
}

如果查询表达式返回枚举,则查询一直到处理枚举时才会执行。
如果枚举被处理多次,查询就会执行多次。
如果在进行遍历之后,查询执行之前数据有改动,则查询会使用新的数据。
如果查询表达式返回标量,查询立即执行,并且把结果保存在查询变量中。
查询表达式由from子句和查询主题组成。
子句必须按照一定的顺序出现。
from子句和select…group子句这两部分是必须的,其他子句是可选的。
select子句在表达式最后。
可以有任意多的from…let…where子句。

from子句

指定要作为数据源使用的数据集合(必须是可枚举的),它还引入了迭代变量。

1
2
from n in list where n>5 select n;
from l1 in list1 from l2 in list2 where l1<l2 select

join子句(联结)

接受两个集合,然后创建一个新的集合,其中每一个元素包含两个原始集合中的元素成员。
必须使用上下文关键字equals来比较字段,不能用==运算符。

1
2
var query = from s in students 
join c in studentsInCourses on s.StID equals c.StID

let子句

接受一个表达式的运算并把它赋值给一个需要在其他运算中使用的标识符

1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args) {
List<int> list1 = new List<int>() { 3, 4, 5, 6 };
List<int> list2 = new List<int>() { 6,7,8,9 };
var res = from l1 in list1
from l2 in list2
let sum = l1+ l2
where sum==12
select new { l1,l2,sum };
foreach(var a in res) {
Console.WriteLine(a);
}
}

where子句

根据之后的运算来去除不符合指定条件的项

orderby子句

接受一个表达式并根据表达式按顺序返回结果项。
可选ascending(升序)descending(降序),默认是升序
可以有任意多个子句,他们必须用逗号分隔。

1
2
3
4
5
6
7
8
9
static void Main(string[] args) {
List<int> list1 = new List<int>() { 16,8,146,95,3,24 };
var res = from l1 in list1
orderby l1
select l1;
foreach(var a in res) {
Console.WriteLine(a);
}
}

select子句

select子句指定应该选择所选对象的哪些部分。
查询结果可以由原始集合的项,原始集合中项的字段或匿名类型组成。

group子句

group…by是可选的,用来指定选择的项如何被分组。
如果项包含在查询的结果中,它们就可以根据某个字段的值进行分组。作为字段分组依据的属性叫做键(key)
group子句返回的不是原始数据源中项的枚举,而是返回可以枚举已经形成的项的分组的可枚举类型。
分组本身是可枚举类型,他们可以枚举实际的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
internal class Program {   
static void Main(string[] args) {
Student[] students = new Student[] {
new Student(){Lname = "三",Fname = "张", age = 18,Major = "计算机网络"},
new Student(){Lname = "四",Fname = "李", age = 18,Major = "数据结构与算法"},
new Student(){Lname = "五",Fname = "王", age = 18,Major = "计算机网络"},
new Student(){Lname = "人甲",Fname = "路", age = 18,Major = "数据库原理"},
new Student(){Lname = "灰乙",Fname = "炮", age = 18,Major = "数据库原理"}
};
var query = from student in students
group student by student.Major;
foreach(var q in query) {
Console.WriteLine(q.Key);
foreach(var s in q) {
Console.WriteLine(s.Fname+s.Lname+"选修"+s.Major);
}
}
}
}
public class Student {
public string Lname { get; set; }
public string Fname { get; set; }
public string Major { get; set; }
public int age { get; set; }
}

每一个分组由一个叫做键的字段区分
每一个分组本身是可枚举类型并且可以枚举它的项。

into语句

into语句是查询延续子句,可以接受查询一部分的结果并赋予一个名字,从而可以在查询的另一部分中使用。

1
2
3
4
5
6
7
8
9
10
11
12
static void Main(string[] args) {
var groupA = new[]{ 3, 4, 5, 6, };
var groupB = new[] { 4,5,6,7 };
var res = from a in groupA
join b in groupB on a equals b
into groupAB
from c in groupAB
select c;
foreach(var r in res) {
Console.WriteLine(r);
}
}

方法语法(标准查询运算符)

标准查询运算符由一系列API方法组成,一些运算符返回IEnumerable对象(或其他序列),而其他运算符返回标量,返回标量的运算符立即执行查询,并返回一个值,而不是一个可枚举类型对象。ToArray()、ToList()等ToCollection运算符也会立即执行。
许多操作都以一个谓词作为参数,谓词是一个方法,它以对象为参数,根据对象是否满足某个条件返回true或false。
被查询的集合对象叫序列,它必须实现IEnumerable<T>接口,其中T是类型。



System.Linq.Enumerable类声明了标准查询运算符方法,然而这些方法不仅仅是普通方法,他们是拓展了IEnumerable<T>泛型类的扩展方法。

常用标准查询运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
internal class Program {   
static void Main(string[] args) {
var group = new[] {1,2,3,4,5,6,7,8,9,0 };
Student[] students = new Student[] {
new Student(){name = "路人甲",age =18 },
new Student(){name = "路人乙",age =18 },
new Student(){name = "路人丙",age =19 },
new Student(){name = "路人丁",age =120 },
};
var res1 = group.Where(e => e > 6);
var res2 = group.Count(e=>e<5);
var res3 = group.Any(e=>e<0);
var res4 = group.Single(e=>e==6);
var res5 = group.SingleOrDefault(e=>e==7);
var res6 = group.First(e=>e>3);
var res7 = group.FirstOrDefault(e=>e>15);
var res8 = group.OrderBy(e=>e);
var res9 = students.OrderBy(e=>e.age);
var res10 = students.OrderByDescending(e=>e.age);
var res11 = students.OrderBy(e => Guid.NewGuid());//随机排序
var res12 = students.OrderBy(e => e.age).ThenBy(e=>e.name);
var res13 = students.OrderBy(e => e.age).ThenByDescending(e=>e.name);
var res14 = group.Skip(2).Take(3);//跳过2条,取2条
var res15 = students.Max(e => e.age);
var res16 = students.Min(e => e.age);
var res17 = students.Average(e => e.age);
var res18 = students.Sum(e => e.age);
var res19 = students.Count();
var res20 = students.GroupBy(e => e.age);
var res21 = students.Select(e => e.age);
var res22 = students.ToList();
var res23 = students.ToArray();
}
}
public class Student {
public string name { get; set; }
public int age { get; set; }
}