C#沉淀-Linq的使用

来自专栏编程改变世界

Linq 可以轻松的查询对象集合。Linq代表语言集成查询,是.NET框架的扩展,支持从资料库、程序对象的集合以及XML文档中查询数据

一个简单的示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Program { static void Main(string[] args) { //创建一个int数组,作为被查询的数据源 int[] numbers = { 1, 2, 3, 4, 5, 18 }; //Linq定义查询,注意,这里只是「定义」而已 IEnumerable<int> lowNum = from nu in numbers where nu < 10 select nu; //遍历lowNum,只有使用lowNum的时候,数据才会被查询出来 //所以,在这里才被执行了查询 foreach (int item in lowNum) { Console.WriteLine(item); } Console.ReadKey(); } }}

针对于不同的数据源,需要实现相应的Linq查询的代码模块,这些代码模块被称作Linq提供程序。在C#中,觉的Linq提供程序有Linq to Object/ Linq to XML/ BLinq(Asp.Net)

匿名类

在深入了解Linq之前,需要先了解一下匿名类,因为在使用Linq语句的时候,会大量的使用匿名类

示例:使用匿名类型创建一个学生类

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Program { static void Main(string[] args) { //创建一个学生类 var student = new { Name = "Jack", Age = 18, Class = "013" }; Console.ReadKey(); } }}

解析:

创建一个匿名类需要用到关键字new,然后直接在后面跟{ }来初始化类中成员(属性),多个成员之间使用逗号分隔;因为没有指定类型,所有在接收创建的对象时,需要用到关键字var,而且必须使用var关键字

  • 匿名类型只能和局部变数配合使用,不能用于类成员
  • 由于匿名类没有名字,所以必须以var关键字作为变数类型
  • 不能设置匿名类型对象的属性,因为匿名类的成员是只读的

在初始化一个匿名类型对象时,其成员的初始化不仅可以使用赋值操作,还可以使用成员访问表达式标识符形式

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Other { public static string Name = "Bob"; } class Program { static void Main(string[] args) { //局部变数,表示班级 string Class = "013"; //创建一个学生类 var student = new { Other.Name, Age = 18, Class }; //访问学生类中成员 Console.WriteLine("My name is "+student.Name); Console.WriteLine("Im "+student.Age+" years old"); Console.WriteLine("My Class is " + student.Class); Console.ReadKey(); } }}

var student = new { Other.Name, Age = 18, Class };的效果等同于var student = new { Name = Other.Name, Age = 18, Class = Class};

如果再声明一个具有相同的参数名、相同的推断类型和相同顺序的匿名类型的话,编译器会重用这个类型直接创建新的实例,而不会创建新的匿名类型

方法语法和查询语法

查询语法:看上去和SQL语句很相似,使用查询表达形式书写

方法语法:使用标准的方法调用

查询语法是声明式的,但未指明如何执行这个查询;方法语法是命令式的,它指明了方法查询调用的顺序

编译器会将使用语法表示的查询翻译为方法调用的形式,在运行时这两种方式没有性能上的差异

先看方法语法与查询语法的示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Other { public static string Name = "Bob"; } class Program { static void Main(string[] args) { //创建一个int数组,作为被查询的数据源 int[] numbers = { 1, 2, 3, 4, 5, 18 }; //查询语法 var _numbers = from nu in numbers where nu < 10 select nu; //方法语法,Where方法的参数使用了Lambda表达式 var _num = numbers.Where(x => x < 10); foreach (int item in _numbers) { Console.WriteLine(item); } foreach (int item in _num) { Console.WriteLine(item); } Console.ReadKey(); } }}

查询变数

Linq查询返回的结果可以是一个枚举,也可以是一个叫做标量的单一值

示例:

//创建一个int数组,作为被查询的数据源int[] numbers = { 1, 2, 3, 4, 5, 18 };//返回一个IEnumerable结果,它可以枚举返回的结果IEnumerable<int> _numbers = from nu in numbers where nu < 10 select nu;//通过Count()方法返回查询结果总数量int _count = (from nu in numbers where nu < 10 select nu).Count();

等号左边的变数叫做查询变数,这里指_numbers_count

查询变数一般使用var类型来让编译器自动推断其返回的类型

如果查询语句返回的是枚举类型,查询变数中是不会包含结果的,只有在真正使用枚举值的时候才会执行查询,并且每次使用枚举值的时候都会执行一次查询语句;而如果查询语句返回的是标题,查询则立即生效,并把结果保存在查询变数中

查询表达式的结构

from子名指定数据源,并且引入迭代变数;迭代变数逐个表示数据源的每一个元素;语法如下:

from [Type] item in ItemsItems表示数据源;item表示数据源中的元素;Type是可选的,表示元素的类型

join子句,联结语句可以结合两个或多个集合中的数据,然后产生一个临时的对象集合,每个集合中都包含原始集合对象中的所有元素,语法如下:

join Identifier in Collection2 on Field1 equqls Field2

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Course//课程类 { public int ID; public int Student_ID; public string Course_Name; } class Student//学生类 { public int ID; public string Name; } class Program { static void Main(string[] args) { //学生类集合 Student[] st = new Student[] { new Student{ID=111,Name="Bob"}, new Student{ID=112,Name="Jack"}, new Student{ID=113,Name="Hong"} }; //课程类集合 Course[] co = new Course[] { new Course{ID=1, Student_ID=111,Course_Name="数学"}, new Course{ID=2, Student_ID=112,Course_Name="语文"}, new Course{ID=3, Student_ID=113,Course_Name="化学"}, new Course{ID=4, Student_ID=112,Course_Name="数学"}, new Course{ID=5, Student_ID=112,Course_Name="生物"} }; //Linq查询语法 var result = from a in st //指定第一个数据源st join b in co on a.ID equals b.Student_ID //联结第二个数据源ot,并用on指定联结条件,equals来指定比较栏位 where b.Course_Name=="数学" //匹配数学课程 select a.Name; //返回名字 foreach (var name in result) { Console.WriteLine("参加数学课程的学生名:"+name); } Console.ReadKey(); } }}

from...let...where片段

可以使用多个from子句指定多个数据源,示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Course//课程类 { public int ID; public int Student_ID; public string Course_Name; } class Student//学生类 { public int ID; public string Name; } class Program { static void Main(string[] args) { //学生类集合 Student[] st = new Student[] { new Student{ID=111,Name="Bob"}, new Student{ID=112,Name="Jack"}, new Student{ID=113,Name="Hong"} }; //课程类集合 Course[] co = new Course[] { new Course{ID=1, Student_ID=111,Course_Name="数学"}, new Course{ID=2, Student_ID=112,Course_Name="语文"}, new Course{ID=3, Student_ID=113,Course_Name="化学"}, new Course{ID=4, Student_ID=112,Course_Name="数学"}, new Course{ID=5, Student_ID=112,Course_Name="生物"} }; //指定多个数据源 var st_co = from a in st from b in co where a.ID == b.Student_ID && b.Course_Name=="数学" select new { a.Name, b.Course_Name };//创建一个匿名类型对象 //访问返回集体中的成员 foreach (var item in st_co) { Console.WriteLine("学生:"+item.Name); Console.WriteLine("课程:"+item.Course_Name); } Console.ReadKey(); } }}

let子句接受一个表达式的运算,并且把它赋值给一个需要在其它地方运算中使用的标识符

示例:

//定义两个数据源int[] number1 = { 1, 2, 3, 4, 5 };int[] numbers2 = { 1, 2, 3, 4, 5, 18 };var nu_array = from a in number1 from b in numbers2 let sum = a + b //使用let子句将第一个集合中的元素与第二个集合中的元素进行相加 where sum == 4 select new { a, b, sum };foreach (var item in nu_array){ Console.WriteLine(item.a + "," + item.b + "," + item.sum);}

where子句根据之后运算来去除不符合指定条件的项,在from...let...where片段中可以有任意多个where子句

示例:

//定义两个数据源int[] number1 = { 1, 2, 3, 4, 5 };int[] numbers2 = { 1, 2, 3, 4, 5, 18 };var nu_array = from a in number1 from b in numbers2 let sum = a + b //使用let子句将第一个集合中的元素与第二个集合中的元素进行相加 where sum == 4 //筛选a+b等于4的所有元素 where a == 2 //再指定a必须等于2,那返回的结果中,b就只能是等于2了 select new { a, b, sum };foreach (var item in nu_array){ Console.WriteLine(item.a + "," + item.b + "," + item.sum);}

orderby子句

orderby子句接受一个表达式,并根据表达式按顺序返回结果;排列的表达式也可以是集合中的成员

  • orderby子句默认是按升序排列的;可以使用ascending显示的指定为升序或使用descending指定为隆序
  • 可以有任意多个子句,之间使用逗号分隔

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Course//课程类 { public int ID; public int Student_ID; public string Course_Name; } class Student//学生类 { public int ID; public string Name; public int Age; } class Program { static void Main(string[] args) { //学生类集合 Student[] st = new Student[] { new Student{ID=111,Name="Bob",Age=12}, new Student{ID=112,Name="Jack",Age=15}, new Student{ID=113,Name="Hong",Age=9} }; //课程类集合 Course[] co = new Course[] { new Course{ID=1, Student_ID=111,Course_Name="数学"}, new Course{ID=2, Student_ID=112,Course_Name="语文"}, new Course{ID=3, Student_ID=113,Course_Name="化学"}, new Course{ID=4, Student_ID=112,Course_Name="数学"}, new Course{ID=5, Student_ID=112,Course_Name="生物"} }; var query = from student in st orderby student.Age // 根据Age栏位进行排序 select student; foreach (var item in query) { Console.WriteLine(string.Format("ID:{0},名字:{1},年龄:{2}",item.ID,item.Name,item.Age)); } Console.ReadKey(); } }}

select...group子句

select子句 指定所选对象的哪部分应该被选择;指定的部分可以是整个数据项,或数据项的一个栏位,或数据项的几个栏位组成的新的对象

group by子句 是可选的,用来指定选择的项如何分组

select子句示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Student//学生类 { public int ID; public string Name; public int Age; } class Program { static void Main(string[] args) { //学生类集合 Student[] st = new Student[] { new Student{ID=111,Name="Bob",Age=12}, new Student{ID=112,Name="Jack",Age=15}, new Student{ID=113,Name="Hong",Age=9} }; var query = from student in st //select student.Name //选择一个栏位 //select new {student.Name, student.Age} //选择多个栏位组成的新对象 select student;// 选择所有的sutdent元素 foreach (var item in query) { Console.WriteLine(string.Format("ID:{0},名字:{1},年龄:{2}",item.ID,item.Name,item.Age)); } Console.ReadKey(); } }}

查询中的匿名类——查询结果可以由原始集合的项、项的某些栏位或匿名类型组成,例如select new {student.Name, student.Age}

group子句将select的对象根据一些标准进行分组

  • 如果项包含在查询语句中,它就可以根据某个栏位的值进行分组;作为分组的依据的属性叫做健(key)
  • gorup将返回可以枚举已经形成的项的分组的可枚举类型
  • 分组本身是可被枚举的

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Student//学生类 { public int ID; public string Name; public int Age; } class Program { static void Main(string[] args) { //学生类集合 Student[] st = new Student[] { new Student{ID=111,Name="Bob",Age=12}, new Student{ID=112,Name="Jack",Age=15}, new Student{ID=113,Name="Json",Age=15}, new Student{ID=114,Name="Hong",Age=9} }; var query = from student in st group student by student.Age; //按照年龄来分组 foreach (var item in query) { Console.WriteLine("年龄组:"+item.Key); //通过Key来找到分组的依据 foreach (var it in item) { Console.WriteLine(" 名字:"+it.Name); } } Console.ReadKey(); } }}

查询延续:into子句

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

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Course//课程类 { public int ID; public int Student_ID; public string Course_Name; } class Student//学生类 { public int ID; public string Name; public int Age; } class Program { static void Main(string[] args) { int[] number1 = { 1, 2, 3, 4, 5 }; int[] numbers2 = { 1, 2, 3, 4, 5, 18 }; var result = from a in number1 join b in numbers2 on a equals b into a_b //通过into将number1与numbers2联合命名为a_b from c in a_b select c; foreach (var item in result) { Console.WriteLine(item); } Console.ReadKey(); } }}

标准查询运算符

  • 被查询的对象叫做序列,它必须实现IEnumberable介面
  • 标准查询运算符使用方法语法
  • 一些运算符返回IEnumberable对象,而其他的一些 运算符返回标量
  • 很多操作都可以一个Lambda表达式做为参数

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Course//课程类 { public int ID; public int Student_ID; public string Course_Name; } class Student//学生类 { public int ID; public string Name; public int Age; } class Program { static void Main(string[] args) { int[] number = { 1, 2, 3, 4, 5 }; //total与hwoMay都是标量 //被操作的对象number是一个序列 //而Sum()与Count()是去处符(方法) int total = number.Sum(); int howMany = number.Count(); Console.ReadKey(); } }}

序列是指实现了IEnumberable介面的类,包括List<>/Dictionary<>/Stack<>/Array等待

共有47个标题运算符,下面列举几个常用的运算符:

  1. Where -- 根据给定的条件对序列进行过滤
  2. Select -- 指定要包含一个对象或
  3. Join -- 对两个序列执行内联结
  4. GroupBy -- 分组序列中的元素
  5. Dinstinct -- 去除序列中的重复项
  6. ToList -- 将序列作为List返回
  7. First -- 返回序列中第一个与条件相匹配的元素
  8. FirstOrDefault -- 返回序列中第一个与条件相匹配的元素,如果匹配不到,就返回第一个元素
  9. Last -- 返回序列中最后一个与条件相匹配的元素
  10. LastOrDefault -- 返回序列中最后一个与条件相匹配的元素,如果匹配不到,就返回最后一个元素
  11. Count -- 返回序列中元素的个数
  12. Sum -- 返回序列中值的总和
  13. Min -- 返回序列中值的最小值
  14. Max -- 返回序列中值的最大值
  15. Average -- 返回序列中值的平均值
  16. Contains -- 返回一个布尔值,指明序列中是否包含某个元素

System.Linq.Enumberable类声明了标准查询运算符方法,它们都扩展了IEnumberable泛型类的扩展方法

  • 由于运算符是泛型方法, 因此每个方法名都具有相关泛型参数(T)
  • 由于运算符是扩展IEnumberable的扩展方法,它们必须满足以下的语法条件
  • 声明为Public和Static
  • 在第一个参数前有this指示器
  • IEnumberable作为第一个参数类型

示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Program { static void Main(string[] args) { int[] number = new int[] { 1, 2, 3, 4, 5 }; //方法语法,数组作为参数 var _count = Enumerable.Count(number); var _first = Enumerable.First(number); //扩展语法,数组被做为被扩展的对象 var __count = number.Count(); var __first = number.First(); Console.ReadKey(); } }}

每一个查询表达式都会被编译器翻译成标准查询运算符的形式,两者可以结合使用,示例:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace CodeForLinq{ class Program { static void Main(string[] args) { int[] number = new int[] { 1, 2, 3, 4, 5 }; int _num = (from nu in number where nu < 4 select nu).Count(); Console.ReadKey(); } }}

推荐阅读:

相关文章