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( http:// 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("I'm "+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 Items
Items
表示数据源;
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("\t名字:"+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个标题运算符,下面列举几个常用的运算符:
- Where -- 根据给定的条件对序列进行过滤
- Select -- 指定要包含一个对象或
- Join -- 对两个序列执行内联结
- GroupBy -- 分组序列中的元素
- Dinstinct -- 去除序列中的重复项
- ToList -- 将序列作为List\返回
- First -- 返回序列中第一个与条件相匹配的元素
- FirstOrDefault -- 返回序列中第一个与条件相匹配的元素,如果匹配不到,就返回第一个元素
- Last -- 返回序列中最后一个与条件相匹配的元素
- LastOrDefault -- 返回序列中最后一个与条件相匹配的元素,如果匹配不到,就返回最后一个元素
- Count -- 返回序列中元素的个数
- Sum -- 返回序列中值的总和
- Min -- 返回序列中值的最小值
- Max -- 返回序列中值的最大值
- Average -- 返回序列中值的平均值
- Contains -- 返回一个布尔值,指明序列中是否包含某个元素
标准查询运算符的签名
System.Linq.Enumberable 类声明了标准查询运算符方法,它们都扩展了 IEnumberable\ 泛型类的扩展方法
扩展方法是公共静态的,尽管定义在一个类中,但目的是为另一个类(第一个形参)增加功能。该参数前必须有关键字
this
- 由于运算符是泛型方法, 因此每个方法名都具有相关泛型参数(T)
- 由于运算符是扩展 IEnumberable 的扩展方法,它们必须满足以下的语法条件
- 声明为Public和Static
- 在第一个参数前有this指示器
- 把 IEnumberable\ 作为第一个参数类型
下面来看 Count 、 Where 、 First 三个方法
//Count
public static int Count<TSource>(this IEnumerable<TSource> source);
//Where
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
//First
public static TSource First<TSource>(this IEnumerable<TSource> source);
-
它们都是Public和Static的
-
它们都是一个泛型方法
-
形参里的this关键字指出了它们量种扩展方法,this后面的泛型类是被扩展的类,在这里可以看出,它们都扩展了
IEnumerable<T>类
如果关于扩展方法的概念是不是很清楚,请参考 泛型 ,敝人本篇文章有关于扩展方法的详细讲解
示例:
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)
//数组是IEnumerable<T>的派生类
int[] number = new int[] { 1, 2, 3, 4, 5 };
//方法语法,数组作为参数
//可以直接通过Enumerable静态类来访问相关方法
var _count = Enumerable.Count(number);
var _first = Enumerable.First(number);
//扩展语法,数组被做为被扩展的对象
//Enumerable扩展了IEnumerable<T>,所以可以在被扩展的类里调用扩展方法
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();
将委托作为参数
很多运算符接受泛型委托作为参数
再来看看 Count 方法,它被重载为两个形式
//第一种:经常使用
public static int Count<TSource>(this IEnumerable<TSource> source);
//第二种,除了指示扩展类外,还接收一个泛型方法作为参数
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
第二种形式的
Count
方法,如果想要传递一个放开方法作为参数,那就只能使用
委托
,因此
Func<TSource, bool> predicate
指的就是一个委托形参(当然,这个委托也可以使用Lambda表达式代替)
示例:
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 };
//Lambda会返回条件为Ture的值,这里会返回奇数,再统计个数
var countOdd = number.Count(n => n % 2 == 1);
Console.WriteLine("奇数个数:{0}",countOdd);
Console.ReadKey();
Linq预定义的委托类型
为了实现由编程人员提供代码来指示标准运算会如何执行它的操作,标准运算符支持将委托作为参数来实现该目标
Linq定义了两套泛型委托类型与标准查询运算符一起使用,即 Fanc 与 Action 委托,各有17个成员
因此在使用的时候要求:
- 我们用作实参的委托对象必须是这些类型或这些形式之一
- TResult代表返回值,并且总是在类型参数列表中的最后一个
示例:
public delegate TResult Func<in T, out TResult>(T arg);
Action 与 Func 相似,只是都没有返回值
使用委托参数的示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CodeForLinq
class Program
//返回是否是奇数
static bool IsOdd(int x)
return x % 2 == 1;
static void Main(string[] args)
int[] number = new int[] { 1, 2, 3, 4, 5 };
//创建一个Func<T, TR>类型委托
//T是类型参数
//TR是返回类型
Func<int, bool> my_func = new Func<int, bool>(IsOdd);
var countOdd = number.Count(my_func);
Console.WriteLine("奇数个数:{0}",countOdd);
Console.ReadKey();
使用Lambda表达式参数的示例
当标准运算符所需要的方法参数只用一次,并且块内代码只有一行,那完成可以使用Lambda表达式来完成
示例 :
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 };
//使用Lambda表达式达到的效果是一样的
var countOdd = number.Count(x => x % 2 == 1);
Console.WriteLine("奇数个数:{0}",countOdd);
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 };
//使用匿名方法
Func<int, bool> my_func = delegate(int x){return x % 2 == 1;};
var countOdd = number.Count(my_func);