C# 基础知识系列-7 Linq详解
前言
在上一篇中简单介绍了linq的入门级用法,这一篇尝试讲解一些更加深入的使用方法,与前一篇的结构不一样的地方是,这一篇我会先介绍linq里的支持方法,然后以实际需求为引导,分别以方法链的形式和类sql的形式写出来。
前置概念介绍
-
predicate<t>
谓词、断言,等价于func<t,bool>
即返回bool的表达式 -
expression<tdelegate>
表达式树,这个类很关键,但是在这里会细说,我们会讲它的一个特殊的泛型类型:expression<func<t,bool>>
这个在某些数据源的查询中十分重要,它代表lambda表达式中一种特殊的表达式,即没有大括号和return
关键字的那种。
我们先准备两个类:
- student/学生类:
/// <summary> /// 学生 /// </summary> public class student { /// <summary> /// 学号 /// </summary> public int studentid { get; set; } /// <summary> /// 姓名 /// </summary> public string name { get; set; } /// <summary> /// 班级 /// </summary> public string class { get; set; } /// <summary> /// 年龄 /// </summary> public int age { get; set; } }
-
subject/科目类:
/// <summary> /// 科目 /// </summary> public class subject { /// <summary> /// 名称 /// </summary> public string name { get; set; } /// <summary> /// 年级 /// </summary> public string grade { get; set; } /// <summary> /// 学号 /// </summary> public int studentid { get; set; } /// <summary> /// 成绩 /// </summary> public int score { get; set; } }
subject 和student通过学号字段一一关联,实际工作中数据表有可能会设计成这。
那么先虚拟两个数据源:ienumerable<student> students
和 ienumerable<subject> subjects
。先忽略这两个数据源的实际来源,因为在开发过程中数据来源有很多种情况,有数据库查询出来的结果、远程接口返回的结果、文件读取的结果等等。不过最后都会整理成ienumerable<t>
的子接口或实现类的对象。
常见方法介绍
where 过滤数据,查询出符合条件的结果
where的方法声明:
public ienumerable<tsource> where<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate)
可以看出不会转换数据类型,通过给定的lambda表达式或者一个方法进行过滤,获取返回true的元素。
示例:
// 获取年纪大于10但不大于12的同学们 list<student> results = students.where(t=>t.age >10 && t.age<= 12).tolist();
注意在调用tolist之后数据才会实质上查询出来。
group 分组,依照指定内容进行分组
group的方法声明有很多种:
最常用的一种是:
public static ienumerable<system.linq.igrouping<tkey,tsource>> groupby<tsource,tkey> (this ienumerable<tsource> source, func<tsource,tkey> keyselector);
示例:
//将学生按照班级进行分组 list<igrouping<string,student>> list = students.groupby(p => p.class).tolist();
orderby/orderbydescending 进行排序,按条件升序/降序
它们是一对方法,一个是升序一个降序,其声明是一样的:
常用的是:
public static system.linq.iorderedenumerable<tsource> orderby<tsource,tkey> (this ienumerable<tsource> source, func<tsource,tkey> keyselector);
示例:
//按年龄的升序排列: list<student> results = students.orderby(p => p.age).tolist(); //按年龄的降序排列: list<student> results = students.orderbydescending(p => p.age).tolist();
first/last 获取数据源的第一个/最后一个
这组方法有两个常用的重载声明:
first:
// 直接获取第一个 public static tsource first<tsource> (this ienumerable<tsource> source); // 获取满足条件的第一个 public static tsource first<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate);
last:
// 直接获取最后一个 public static tsource last<tsource> (this ienumerable<tsource> source); // 获取最后一个满足条件的元素 public static tsource last<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate);
示例:
student student = students.first();// 等价于 students[0]; student student = students.first(p=>p.class == "一班");//获取数据源中第一个一班的同学 student student = students.last();//最后一个学生 student student = students.last(p=>p.class == "三班");//获取数据源中最后一个三班的同学
注意:
- 在某些数据源中使用last会报错,因为对于一些管道类型的数据源或者说异步数据源,程序无法确认最后一个元素的位置,所以会报错。解决方案:先使用orderby对数据源进行一次排序,使结果与原有顺序相反,然后使用first获取
- 当数据源为空,或者不存在满足条件的元素时,调用这组方法会报错。解决方案:调用firstordefault/lastordefault,这两组方法在无法查询到结果时会返回一个默认值。
any/all 是否存在/是否都满足
any:是否存在元素满足条件
有两个版本,不过意思可能不太一样:
public static bool any<tsource> (this ienumerable<tsource> source);//数据源中是否有数据 //================ //是否存在满足条件的数据 public static bool any<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate);
all :是否都满足条件:
public static bool any<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate);
示例:
// 是否有学生 bool isany = students.any(); // 是否有五班的同学 bool isfive = students.any(p=>p.class == "五班"); // 是否所有学生的年纪都不小于9岁 bool isall = students.all(p=>p.age >= 9);
skip 略过几个元素
skip一共有三个衍生方法:
第一个:skip 自己: 略过几个元素,返回剩下的元素内容
public static ienumerable<tsource> skip<tsource> (this ienumerable<tsource> source, int count);
第二个:skiplast,从尾巴开始略过几个元素,返回剩下的元素内容
public static ienumerable<tsource> skiplast<tsource> (this ienumerable<tsource> source, int count);
第三个:skipwhile,跳过满足条件的元素,返回剩下的元素
public static ienumerable<tsource> skipwhile<tsource> (this ienumerable<tsource> source, func<tsource,bool> predicate);
示例:
// 不保留前10个学生 list<student> results = students.skip(10).tolist(); // 不保留后10个学生 list<student> results = students.skiplast(10).tolist(); // 只要非一班的学生 list<student> results = students.skipwhere(p=>p.class=="一班").tolist(); //上一行代码 等价于 = students.where(p=>p.class != "一班").tolist();
take 选取几个元素
take与skip一样也有三个衍生方法,声明的参数类型也一样,这里就不对声明做介绍了,直接上示例。
//选取前10名同学 list<student> results = students.take(10).tolist(); // 选取最后10名同学 list<student> results = students.takelast(10).tolist(); //选取 一班的学生 list<student> results = students.takewhile(p=>p.class=="一班").tolist(); // 上一行 等价于 = students.where(p=>p.class=="一班").tolist();
在使用linq写分页的时候,就是联合使用take和skip这两个方法:
int pagesize = 10;//每页10条数据 int pageindex = 1;//当前第一页 list<student> results = students.skip((pageindex-1)*pagesize).take(pagesize).tolist();
其中 pageindex可以是任意大于0 的数字。take和skip比较有意思的地方就是,如果传入的数字比数据源的数据量大,根本不会爆粗,只会返回一个空数据源列表。
select 选取
官方对于select的解释是,将序列中的每个元素投影到新的表单里。我的理解就是,自己 定义一个数据源单个对象的转换器,然后按照自己的方式对数据进行处理,选择出一部分字段,转换一部分字段。
所以按我的理解,我没找到java8的同效果方法。(实际上java用的是map,所以没找到,