在上一篇中简单介绍了Linq的入门级用法,这一篇尝试讲解一些更加深刻的使用方法,与前一篇的结构不同的地方是,这一篇我会先介绍Linq里的支持方法,而后以实际需求为引导,分别以方法链的形式和类SQL的形式写出来。html
Predicate<T>
谓词、断言,等价于 Func<T,bool>
即返回bool的表达式Expression<TDelegate>
表达式树,这个类很关键,可是在这里会细说,咱们会讲它的一个特殊的泛型类型:Expression<Func<T,bool>>
这个在某些数据源的查询中十分重要,它表明lambda表达式中一种特殊的表达式,即没有大括号和return
关键字的那种。咱们先准备两个类:java
/// <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经过学号字段一一关联,实际工做中数据表有可能会设计成这。c#
那么先虚拟两个数据源:IEnumerable<Student> students
和 IEnumerable<Subject> subjects
。先忽略这两个数据源的实际来源,由于在开发过程当中数据来源有不少种状况,有数据库查询出来的结果、远程接口返回的结果、文件读取的结果等等。不过最后都会整理成IEnumerable<T>
的子接口或实现类的对象。api
where的方法声明:app
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以后数据才会实质上查询出来。this
Group的方法声明有不少种:spa
最经常使用的一种是:
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();
它们是一对方法,一个是升序一个降序,其声明是同样的:
经常使用的是:
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:
// 直接获取第一个 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 == "三班");//获取数据源中最后一个三班的同窗
注意:
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 本身: 略过几个元素,返回剩下的元素内容
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与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的解释是,将序列中的每一个元素投影到新的表单里。个人理解就是,本身 定义一个数据源单个对象的转换器,而后按照本身的方式对数据进行处理,选择出一部分字段,转换一部分字段。
因此按个人理解,我没找到java8的同效果方法。(实际上java用的是map,因此没找到,:-D)
public static System.Collections.Generic.IEnumerable<TResult> Select<TSource,TResult> (this IEnumerable<TSource> source, Func<TSource,TResult> selector);
示例:
// 选出班级和姓名 List<object> results = students.Select(p => new { p.Class, p.Name }).ToList();
Linq 里有几个须要注意的简单运算操做,这部分在使用中很常见。
Max获取数据源中最大的一个,不过只能是数字类型的,其余类型由于不能直接比较大小因此能够有替代方法,就是先排序取第一个。
如下是Max方法的两个重载版本:
public static int Max (this IEnumerable<int> source); public static int Max <TSource>(this IEnumerable<TSource> source,Func<TSource,int> selector);
示例:
//查询学生中最大的年纪是多少 int maxAge = students.Select(t=>t.Age).Max();
方法相似与Max,不过与之不一样的是获取最小的一个,不能应用于非数字类型。
示例:
// 查询学生中最小的年纪是多少 int minAge = students.Select(t=> t.Age).Min(); //======= int minAge = students.Min(p=>p.Age);
与 Max/Min是同样类型的方法,依旧不能应用于非数字类型。
示例:
// 查询学生的评价年纪 int averageAge = students.Select(t=>t.Age).Average(); int averageAge = students.Average(p=>p.Age);
对数据源进行求和或者对数据源的某个字段进行求和,仍是不能对非数字类型进行求和
示例:
// 一个没有实际意义的求和,学生的年龄总和 int sumAge = students.Select(t=>t.Age).Sum(); // int sumAge = students.Sum(p=>p.Age);
判断数据源中是否包含某个元素,返回一个bool值,若是包含则返回true,若是不包含则返回false。该方法有两个重载版本,一个是使用默认的Equals
方法,一个是指定一个相等性比较器实现类。
public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value); //传入相等性比较器的 public static bool Contains<TSource> (this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer);
值得注意的是,这里的相等比较器是一个接口,也就是说须要使用类来实现这个方法。一般在实际开发过程当中,咱们会在TSource这个数据源所表明的类上增长 IEqualityCompare的实现。
示例1:
Student student1 = new Student();// 初始化一个学生类 Student student2 = students.First();// 从数据源中取一个 bool isContains = students.Contains(student1);// 返回 false, bool isContains2 = students.Contains(student2);// 返回 true
说明: 类的默认相等比较是比较是不是同一个对象,即返回的
示例2:
建立一个相等性比较器,值得注意的是,相等性比较器有两个方法,一个是比较元素是否相等,一个是返回元素的HashCode,这两个方法必须在判断元素是否相等上保持结果一致。
public class StudentEqualityCompare: IEqualityComparer<Student> { public bool Equals(Student x, Student y) { // 省略逻辑 } public int GetHashCode(Student obj) { //省略逻辑 } }
使用:
StudentEqualityCompare compare = new StudentEqualityCompare(); Student student = students.First(); bool isContains = students.Contains(student, compare);
这是一组行为同样的方法,就是对数据源进行计数,不一样的是Count返回int,LongCount返回long。
它们的声明有如下两种,这里选了Count的声明:
public static int Count<TSource> (this IEnumerable<TSource> source); public static int Count<TSource> (this IEnumerable<TSource> source, Func<TSource,bool> predicate);
示例:
int count = students.Count();//返回一共有多少个学生 int count = students.Count(p=>p.Class=="一班");// 统计一班一共有多少学生
以前介绍了单个数据源的操做方法,这些方法不会让数据源发生变化,更多的对数据源进行过滤和选择或者统计。如今介绍几个对多个数据源进行操做的方法。
联合另外一个数据源,意思就是把两个数据源合并到一个里面,去掉重复的元素,只保留不重复的元素,并返回这个结果集。
与Contains方法差很少,这个方法有两个重载的版本:
public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Union<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
先假设一个业务场景:
学校举办运动会,如今教务处收到了田径组 500米跑的报名名单和跳远的报名名单,须要看看一共有哪些学生报名了这两项赛事。
// 省略数据源,田径组的名单 IEnumerable<Student> students1 = new List<Student>(); //省略数据源来源,跳远组的名单 IEnumerable<Student> students2 = new List<Student>(); List<Student> all = students1.Union(student2).ToList();
这时候简单统计了一下全部人,可是后来教务处在核对的时候,发现有的人名重复了,须要判断是不是一我的,这时候就必须建立一个相等比较器了。
List<Student> all = students1.Union(student2,compare).ToList(); // 省略compare的实现,具体可参照Contains的比较器
获取同时存在于两个集合中的元素,与Union相似。
方法的声明以下:
public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Intersect<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
继续以前的业务场景,如今教务处须要知道有哪些同窗同时报名了两个比赛
List<Student> students = students1.Intersect(students2).ToList();
获取只存在于第一个集合的元素,从第一个集合中去除同时存在与第二个集合的元素,并返回。
方法的声明以下:
public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second); public static IEnumerable<TSource> Except<TSource> (this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer);
示例:
继续业务描述,教务处要一份只报名了500米的学生名单:
List<Student> students = students1.Except(students2).ToList();
数据源中的元素本来有必定的顺序,这个方法能够将数据源中的顺序翻转过来,本来是最后一个的变成了第一个
,第一个变成了最后一个。
简单示例:
char[] apple = { 'a', 'p', 'p', 'l', 'e' }; char[] reversed = apple.Reverse().ToArray();
对数据源进行去重,而后返回去重以后的结果。一样,这个方法有两个重载版本,一个有比较器,一个没有比较器。
// 不用比较器的 public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source); // 设置比较器 public static IEnumerable<TSource> Distinct<TSource> (this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer);
示例:
先描述一个可能会出现的场景,每一个班级在各个赛事组提交报名信息的时候有点混乱,500米的负责老师把一个班的名单多录了一次,可是学生已经乱序了,如今须要把多录的去掉,也就是对数据进行去重。
List<Student> students = students1.Distinct();
以前的方法基本都是对一个类型的数据源进行操做,不会涉及其余类型的数据源。如今介绍一下怎么关联多个类型的数据源,相似于SQL里的多表连接查询。
按照必定的逻辑将两个数据源关联到一块儿,而后选择出须要的数据。
方法有这几个重载版本:
public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector); // public static IEnumerable<TResult> Join<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,TInner,TResult> resultSelector, IEqualityComparer<TKey> comparer);
这个方法的参数比较多,咱们大概介绍一下这个方法的全部参数:
类型参数
TOuter 第一个序列中的元素的类型。
TInner 第二个序列中的元素的类型。
TKey 选择器函数返回的键的类型。
TResult 结果元素的类型。
参数
outer IEnumerable<TOuter> 要联接的第一个序列。
inner IEnumerable<TInner> 要与第一个序列联接的序列。
outerKeySelector Func<TOuter,TKey> 用于从第一个序列的每一个元素提取联接键的函数。
innerKeySelector Func<TInner,TKey> 用于从第二个序列的每一个元素提取联接键的函数。
resultSelector Func<TOuter,TInner,TResult> 用于从两个匹配元素建立结果元素的函数。
comparerIEqualityComparer<TKey> 用于对键进行哈希处理和比较的 IEqualityComparer。
示例:
假设前天语文老师组织了一场考试,由于是模拟正式考试,因此答题纸上学生都只写了学号,如今须要把考试成绩和学生们联系在一块儿
List<object> results = students.Join(subjects, p => p.StudentId, s => s.StudentId, (p, s) => new { Student = p, Subject = s }).ToList(); /** 返回一个学生和科目的匿名对象,不过被我用object接了,这里会有一个问题,若是有兴致能够提早了解一下C#的var关键字和匿名对象,这部分将会放在C#基础系列补全篇讲解 */
基于键值等同性将两个序列的元素进行关联,并对结果进行分组。以上是官方介绍,我在开发过程当中并无使用过这个方法,不过这个方法彻底能够认为是Join和Group的组合体,即先进行了一次Join而后又对数据进行一次分组。
方法声明:
// 使用默认比较器 public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector); //设置比较器 public static IEnumerable<TResult> GroupJoin<TOuter,TInner,TKey,TResult> (this IEnumerable<TOuter> outer, IEnumerable<TInner> inner, Func<TOuter,TKey> outerKeySelector, Func<TInner,TKey> innerKeySelector, Func<TOuter,IEnumerable<TInner>,TResult> resultSelector, IEqualityComparer<TKey> comparer);
类型参数
TOuter 第一个序列中的元素的类型。
TInner 第二个序列中的元素的类型。
TKey 键选择器函数返回的键的类型。
TResult 结果元素的类型。
参数
outer IEnumerable<TOuter> 要联接的第一个序列。
inner IEnumerable<TInner> 要与第一个序列联接的序列。
outerKeySelector Func<TOuter,TKey> 用于从第一个序列的每一个元素提取联接键的函数。
innerKeySelector Func<TInner,TKey> 用于从第二个序列的每一个元素提取联接键的函数。
resultSelector Func<TOuter,IEnumerable<TInner>,TResult> 用于从第一个序列的元素和第二个序列的匹配元素集合中建立结果元素的函数。
comparer IEqualityComparer<TKey> 用于对键进行哈希处理和比较的 IEqualityComparer。
如下是官方给的示例:
class Person { public string Name { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } public static void GroupJoinEx1() { Person magnus = new Person { Name = "Hedlund, Magnus" }; Person terry = new Person { Name = "Adams, Terry" }; Person charlotte = new Person { Name = "Weiss, Charlotte" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; List<Person> people = new List<Person> { magnus, terry, charlotte }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, daisy }; // Create a list where each element is an anonymous // type that contains a person's name and // a collection of names of the pets they own. var query = people.GroupJoin(pets, person => person, pet => pet.Owner, (person, petCollection) => new { OwnerName = person.Name, Pets = petCollection.Select(pet => pet.Name) }); foreach (var obj in query) { // Output the owner's name. Console.WriteLine("{0}:", obj.OwnerName); // Output each of the owner's pet's names. foreach (string pet in obj.Pets) { Console.WriteLine(" {0}", pet); } } } /* This code produces the following output: Hedlund, Magnus: Daisy Adams, Terry: Barley Boots Weiss, Charlotte: Whiskers */
以上是关于Linq的全部方法内容,可是这仍然不是Linq的所有。后续还会有一篇关于Linq的另外一种查询方式的内容文章。
更多内容烦请关注个人博客
原文出处:https://www.cnblogs.com/c7jie/p/12632800.html