欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C# 基础知识系列-7 Linq详解

程序员文章站 2022-08-30 09:23:06
前言 在上一篇中简单介绍了Linq的入门级用法,这一篇尝试讲解一些更加深入的使用方法,与前一篇的结构不一样的地方是,这一篇我会先介绍Linq里的支持方法,然后以实际需求为引导,分别以方法链的形式和类SQL的形式写出来。 前置概念介绍 1. 谓词、断言,等价于 即返回bool的表达式 2. 表达式树, ......

前言

在上一篇中简单介绍了linq的入门级用法,这一篇尝试讲解一些更加深入的使用方法,与前一篇的结构不一样的地方是,这一篇我会先介绍linq里的支持方法,然后以实际需求为引导,分别以方法链的形式和类sql的形式写出来。

前置概念介绍

  1. predicate<t> 谓词、断言,等价于 func<t,bool> 即返回bool的表达式
  2. expression<tdelegate> 表达式树,这个类很关键,但是在这里会细说,我们会讲它的一个特殊的泛型类型:expression<func<t,bool>> 这个在某些数据源的查询中十分重要,它代表lambda表达式中一种特殊的表达式,即没有大括号和return关键字的那种。

我们先准备两个类

  1. 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; }
}
  1. 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> studentsienumerable<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,所以没找到,