当前位置: 移动技术网 > IT编程>开发语言>c# > C# 基础知识系列-7 Linq详解

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

2020年04月05日  | 移动技术网IT编程  | 我要评论

前言

在上一篇中简单介绍了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,所以没找到,

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网