1、揭开linq的神秘面纱
(一)概述
LINQ的全称是Language Integrated Query,中文译成“语言集成查询”。LINQ做为一种查询技术,首先要解决数据源的封装,大体使用了三大组件来实现这个封装,分别是LINQ to Object、LINQ to ADO.NET、LINQ to XML。它们和.NET语言的关系以下html
要使用LINQ来编程,首先要学习使用LINQ的子句以及由查询语法构成的查询表达式。C#3.0和VB9开始将这种查询语法引入到了编程语言,并新增了一系列的关键字。但对于CLR自己来讲,它并不了解查询语法,它能理解的是由编程语言的编译器将这种查询语法转换成的方法。这些方法叫“标准查询运算符”,它们具备相似这样的名—Where、Select、GroupBy、Join。下面就以C#为例,从编程语言的层面来具体介绍这些查询语法(注意VB9也支持这种查询语法)。数据库
LINQ的查询由3基本部分组成:获取数据源,建立查询,执行查询express
// 1,获取数据源 List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; // 2,建立查询 var numQuery = from num in numbers where num % 2 == 0 select num; // 3,执行查询 foreach (var num in numQuery) { Console.WriteLine("{0,1}", num); }
下图显示了完整的查询操做。在 LINQ 中,查询的执行与查询自己大相径庭;换句话说,若是只是建立查询变量,则不会检索任何数据。编程
如上例所示,Linq的数据源要求必须实现IEnumerable或IEnumerable<T>接口,数组隐式支持这个接口。numQuery叫作查询变量,它存储了一个查询表达式。注意,声明查询变量并不会执行查询,真正的执行查询延迟到了foreach语句中。数组
linq的机制用到的详细知识点请参考:https://www.cnblogs.com/liulun/archive/2013/02/26/2909985.html服务器
1. from子句编程语言
建立一个LINQ表达式必需要以from子句开头。函数
1.1 单个from子句学习
string[] values = { "中国", "日本", "美国", "菲律宾", "越南" }; //查询包含“国”的字符串 var valueQuery = from v in values where v.IndexOf("国") > 0 select v; foreach (var v in valueQuery) { Console.WriteLine("{0,1}", v); }
在这个LINQ表达式的from子句中,v叫作范围变量,values是数据源。v的做用域存在于当前的LINQ表达式,表达式之外不能访问这个变量。where用来筛选元素,select用于输出元素。这里的范围变量v,和foreach语句中得隐式变量v均可以由编译器推断出其类型。
运行的结果以下:this
中国
美国
使用LINQ查询List<T>集合
public class CustomerInfo { public string Name { get; set; } public int Age { get; set; } public string Tel { get; set; } } private void formExpDemo2() { //这里用了,对象和集合初始化器 List<CustomerInfo> customers = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="诸葛菲菲", Age=23, Tel ="1380524****"} }; //查询年龄大于20的客户,注意这里的范围变量用了显示类型CustomerInfo var query = from CustomerInfo ci in customers where ci.Age > 20 select ci; foreach (CustomerInfo ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); } }
结果:
姓名:欧阳晓晓 年龄:35 电话:1330708**** 姓名:诸葛菲菲 年龄:23 电话:1380524****
1.2 复合from子句
在查询数据源中,元素的属性是一个集合时,可使用复合from子句对这个属性集合查询。好比,一个客户,可能有多个电话。
public class CustomerInfo { public string Name { get; set; } public int Age { get; set; } public List<string> TelTable { get; set; } } private void formExpDemo() { List<CustomerInfo> customers = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, TelTable=new List<string>{"1330708****","1330709****"}}, new CustomerInfo{ Name="上官飘飘", Age=17, TelTable=new List<string>{"1592842****","1592843****"}}, new CustomerInfo{ Name="诸葛菲菲", Age=23, TelTable=new List<string>{"1380524****","1380525****"}} }; //查询包含电话号码1592842****的客户 var query = from CustomerInfo ci in customers from tel in ci.TelTable where tel.IndexOf("1592842****") > -1 select ci; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1}", ci.Name, ci.Age); foreach (var tel in ci.TelTable) { Console.WriteLine(" 电话:{0}", tel); } } }
结果:
姓名:上官飘飘 年龄:17 电话:1592842**** 电话:1592843****
1.3 多个from子句
多个from子句查询和复合from子句从字面上看彷佛同样,实际上是不一样的操做。复合from子句查询的是单个数据源中的子元素的集合,而多个from子句,是载入多个数据源进行查询。
private void formExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="诸葛菲菲", Age=23, Tel ="1380524****"} }; List<CustomerInfo> clist2 = new List<CustomerInfo> { new CustomerInfo{ Name="令狐冲", Age=25, Tel ="1330708****"}, new CustomerInfo{ Name="东方不败", Age=35, Tel ="1592842****"}, new CustomerInfo{ Name="任盈盈", Age=23, Tel ="1380524****"} }; //在clist中查找Age大于20的客户, //在clist2中查找Age小于30的客户 var query = from customer in clist where customer.Age > 20 from customer2 in clist2 where customer2.Age < 30 select new { customer, customer2 }; foreach (var ci in query) { Console.WriteLine("{0} {1}", ci.customer.Name,ci.customer2.Name); } }
在select语句中,咱们用了匿名类型来存储筛选出的元素,这样获得的彻底是一个交叉联接表,有点相似于SQL中的笛卡尔乘积。
输出的结果:
欧阳晓晓 令狐冲
欧阳晓晓 任盈盈
诸葛菲菲 令狐冲
诸葛菲菲 任盈盈
二、where子句
where子句的做用就是筛选元素,除了开始和结束位置,where子句几乎能够出如今LINQ表达式的任意位置。一个LINQ表达式中能够有where子句,也能够没有;能够有一个,能够有多个;多个where子句之间的关系至关于逻辑“与”,每一个where子句能够包含1个或多个逻辑表达式,这些条件成为“谓词”,多个谓词之间用布尔运算符隔开,好比逻辑“与”用&&,逻辑“或”用||,而不是用SQL中的AND或OR。
2.1 常见的where子句查询
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐冲", Age=23, Tel ="1380524****"} }; //查询名字是3个字或者姓“令”的,但年龄大于20的客户 var query = from customer in clist where (customer.Name.Length == 3 || customer.Name.Substring(0, 1) == "令") && customer.Age > 20 select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); }
结果:
姓名:令狐冲 年龄:23 电话:1380524****
2.2 在where子句中使用自定义函数
private void whereExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐冲", Age=23, Tel ="1380524****"} }; //查询名字是3个字而且姓“令”的客户 var query = from customer in clist where (customer.Name.Length == 3 && CheckName(customer.Name)) select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); } } private bool CheckName(string name) { if (name.Substring(0, 1) == "令") return true; else return false; }
结果:
姓名:令狐冲 年龄:23 电话:1380524****
2.3 动态谓词的筛选
上面的几个例子都是给定了查询谓词而后进行查询,有时候谓词的数量可能并不固定,是随状况变化的。例如:一组名字多是运行时动态指定的。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐冲", Age=23, Tel ="1380524****"} }; //定义动态的谓词数组,这个数组应该由实际运行环境生成 string[] names = { "令狐冲", "任盈盈", "杨过", "小龙女", "欧阳晓晓" }; //查询在给定谓词数组里存在的客户 var query = from customer in clist where names.Contains(customer.Name) select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); }
结果:
姓名:欧阳晓晓 年龄:35 电话:1330708**** 姓名:令狐冲 年龄:23 电话:1380524****
3. select子句
3.1 输出查询结果
最简单的select就是直接输出from子句创建的那个范围变量:
var query = from customer in clist where names.Contains(customer.Name) select customer;
也能够输出范围变量类型中得某个属性:
select customer.Name;
或者修改一下再输出:
select customer.Name.Replace("gg","mm");
或者干脆使用一个自定义的函数,把范围变量传进去,输出处理后的结果:
select MyFunction(customer.Name);
3.2 对查询结果进行投影
public class MyCustomerInfo { public string Name { get; set; } public string Tel { get; set; } } private void whereExpDemo() { List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="令狐冲", Age=23, Tel ="1380524****"} }; //定义动态的谓词数组,这个数组应该由实际运行环境生成 string[] names = { "令狐冲", "任盈盈", "杨过", "小龙女", "欧阳晓晓" }; //查询在给定谓词数组里存在的客户 var query = from customer in clist where customer.Age < 30 select new MyCustomerInfo { Name = customer.Name, Tel = customer.Tel }; foreach (var ci in query) { Console.WriteLine("姓名:{0} 电话:{1} 类型{2}", ci.Name, ci.Tel,ci.GetType().FullName); } }
上例中,在select子句中用对象初始化器生成了新的数据类型,从而进行了数据转换,使元素变成了MyCustomerInfo类型。
姓名:上官飘飘 电话:1592842**** 类型LinqDemo.Form1+MyCustomerInfo 姓名:令狐冲 电话:1380524**** 类型LinqDemo.Form1+MyCustomerInfo
4. group子句
按照语法的规定,LINQ表达式必须以from子句开头,以select或group子句结束,因此除了使用select子句外,也可使用guoup子句来返回元素分组后的结果。group子句返回的是一个IGrouping<TKey,TElement>泛型接口的对象集合,下面先了解下这个接口。
4.1 IGrouping<TKey,TElement>泛型接口
这个接口表示具备公共键的对象集合,它的原型以下:
public interface IGrouping<TKey, TElement> : IEnumerable<TElement>, IEnumerable
TKey是键的对象类型,在用于group子句的时候,数据类型会有编译器推断出来,它通常用于存储分组的键值;TElement是指的对象类型,用于存储分组的结果,变量基于这个接口的类型就是遍历这个值。
4.2 分组查询
分组查询对于关系型数据库是很是常见的一种操做,但在没有LINQ以前,对内存的对象进行分组倒是一件很是麻烦的事情。如今,在LINQ表达式中只须要使用group子句就能够轻松完成对内存对象的分组。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="欧阳锦鹏", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官无忌", Age=23, Tel ="1380524****"} }; //按照名字的前2个字进行分组 var query = from customer in clist group customer by customer.Name.Substring(0, 2); foreach (IGrouping<string,CustomerInfo> group in query) { Console.WriteLine("分组键:{0}",group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 电话:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); }
上例代码,按照form子句创建的范围变量customer的Name属性的前两个字做为键值进行分组。因此TKey的类型是一个字符串类型。
分组键:欧阳
姓名:欧阳晓晓 电话:1330708**** 姓名:欧阳锦鹏 电话:1330708**** *************************************** 分组键:上官 姓名:上官飘飘 电话:1592842**** 姓名:上官无忌 电话:1380524**** ***************************************
再看一个分组的例子:
//按照年龄是否大于20分组 var query = from customer in clist group customer by customer.Age > 20; foreach (var group in query) { Console.WriteLine("分组键:{0}",group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 电话:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); }
group子句用了一个布尔表达式,因此IGrouping<TKey,TElement>的TKey变成了一个bool型。而且循环遍历的时候能够用var代替IGrouping的声明:
foreach (var group in query)
分组键:True
姓名:欧阳晓晓 电话:1330708**** 姓名:欧阳锦鹏 电话:1330708**** 姓名:上官无忌 电话:1380524**** *************************************** 分组键:False 姓名:上官飘飘 电话:1592842**** ***************************************
5. into子句
into子句做为一个临时标识符,用于select,group,join子句中。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="欧阳锦鹏", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官无忌", Age=23, Tel ="1380524****"} }; //按照名字的前两个字进行分组,再用分组Key进行排序 var query = from customer in clist group customer by customer.Name.Substring(0, 2) into gpcustomer orderby gpcustomer.Key descending select gpcustomer; Console.WriteLine("into 用于group子句"); foreach (var group in query) { Console.WriteLine("分组键:{0}", group.Key); foreach (var ci in group) { Console.WriteLine("姓名:{0} 电话:{1}", ci.Name, ci.Tel); } Console.WriteLine("***************************************"); } var query2 = from customer in clist select new { NewName = customer.Name, NewAge = customer.Age } into newCustomer orderby newCustomer.NewAge select newCustomer; Console.WriteLine("into 用于select子句"); foreach (var ci in query2) { Console.WriteLine("{0} 年龄:{1}", ci.NewName, ci.NewAge); }
into子句提供了一个临时标识符,它存储了into子句前面的查询内容,使它后面的子句能够方便的使用,对其进行再次查询,投影等操做。
执行结果:
into 用于group子句
分组键:上官
姓名:上官飘飘 电话:1592842**** 姓名:上官无忌 电话:1380524**** *************************************** 分组键:欧阳 姓名:欧阳晓晓 电话:1330708**** 姓名:欧阳锦鹏 电话:1330708**** *************************************** into 用于select子句 上官飘飘 年龄:17 上官无忌 年龄:23 欧阳晓晓 年龄:35 欧阳锦鹏 年龄:35
LINQ能够按元素的一个或多个属性对元素进行排序。LINQ表达式的排序方式分为OrderBy、OrderByDescending、ThenBy、ThenByDescending这四种。
6.1 OrderBy和OrderByDescending
OrderBy用于按元素的值进行升序,语法:
orderby 用于排序的元素的表达式
OrderByDescending用于按元素的值进行降序,语法:
orderby 用于排序的元素的表达式 descending
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="欧阳锦鹏", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官无忌", Age=23, Tel ="1380524****"} }; //按照年龄升序 var query = from customer in clist orderby customer.Age select customer; Console.WriteLine("按年龄升序排列"); foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); } //按照年龄降序 var query2 = from customer in clist orderby customer.Age descending select customer; Console.WriteLine("\n按年龄降序排列"); foreach (var ci in query2) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); }
运行结果:
按年龄升序排列
姓名:上官飘飘 年龄:17 电话:1592842**** 姓名:上官无忌 年龄:23 电话:1380524**** 姓名:欧阳晓晓 年龄:35 电话:1330708**** 姓名:欧阳锦鹏 年龄:35 电话:1330708**** 按年龄降序排列 姓名:欧阳晓晓 年龄:35 电话:1330708**** 姓名:欧阳锦鹏 年龄:35 电话:1330708**** 姓名:上官无忌 年龄:23 电话:1380524**** 姓名:上官飘飘 年龄:17 电话:1592842****
6.2 ThenBy和ThenByDescending
ThenBy和ThenByDescending用于对元素进行次要排序。基本语法:
orderby 用于排序的元素表达式,用于排序的元素表达式 orderby 用于排序的元素表达式,用于排序的元素表达式 descending
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黄蓉", Age=17, Tel ="1300524****"} }; //按照年龄升序,再按名字的字数次要排序 var query = from customer in clist orderby customer.Age,customer.Name.Length select customer; Console.WriteLine("按年龄排列,按名字字数进行次要排序"); foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); } //按照年龄升序,再按名字的字数降序次要排序 var query2 = from customer in clist orderby customer.Age, customer.Name.Length descending select customer; Console.WriteLine("\n按年龄排列,按名字字数进行降序次要排序"); foreach (var ci in query2) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); } //按照年龄升序,再按名字的字数降序要排序,在按电话号码进行第三条件排序 var query3 = from customer in clist orderby customer.Age, customer.Name.Length,customer.Tel select customer; Console.WriteLine("\n按年龄,名字字数,电话号码排序"); foreach (var ci in query3) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); }
执行结果:
按年龄排列,按名字字数进行次要排序
姓名:郭靖 年龄:17 电话:1330708**** 姓名:黄蓉 年龄:17 电话:1300524**** 姓名:上官飘飘 年龄:17 电话:1592842**** 姓名:欧阳晓晓 年龄:35 电话:1330708**** 按年龄排列,按名字字数进行降序次要排序 姓名:上官飘飘 年龄:17 电话:1592842**** 姓名:郭靖 年龄:17 电话:1330708**** 姓名:黄蓉 年龄:17 电话:1300524**** 姓名:欧阳晓晓 年龄:35 电话:1330708**** 按年龄,名字字数,电话号码排序 姓名:黄蓉 年龄:17 电话:1300524**** 姓名:郭靖 年龄:17 电话:1330708**** 姓名:上官飘飘 年龄:17 电话:1592842**** 姓名:欧阳晓晓 年龄:35 电话:1330708****
7. let子句
let子句用于在LINQ表达式中存储子表达式的计算结果。let子句建立一个范围变量来存储结果,变量被建立后,不能修改或把其余表达式的结果从新赋值给它。此范围变量能够再后续的LINQ子句中使用。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黄蓉", Age=17, Tel ="1300524****"} }; //姓“郭”或“黄”的客户 var query = from customer in clist let g = customer.Name.Substring(0,1) where g == "郭" || g == "黄" select customer; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} 电话:{2}", ci.Name, ci.Age, ci.Tel); }
使用let 创建了个范围变量,这个范围变量在后续的where子句中使用,若是不使用let子句,where子句的表达式将写成这样:
where customer.Name.Substring(0, 1) == "郭" || customer.Name.Substring(0, 1) == "黄"
姓名:郭靖 年龄:17 电话:1330708**** 姓名:黄蓉 年龄:17 电话:1300524****
8. join子句
若是一个数据源中元素的某个属性能够跟另外一个数据源中元素的属性进行相等比较,那么这两个数据源能够用join子句进行关联。jion子句用equals关键字进行比较,而不是常见的==。
List<CustomerInfo> clist = new List<CustomerInfo> { new CustomerInfo{ Name="欧阳晓晓", Age=35, Tel ="1330708****"}, new CustomerInfo{ Name="上官飘飘", Age=17, Tel ="1592842****"}, new CustomerInfo{ Name="郭靖", Age=17, Tel ="1330708****"}, new CustomerInfo{ Name="黄蓉", Age=17, Tel ="1300524****"} }; List<CustomerTitle> titleList = new List<CustomerTitle> { new CustomerTitle{ Name="欧阳晓晓", Title="歌手"}, new CustomerTitle{ Name="郭靖", Title="大侠"}, new CustomerTitle{ Name="郭靖", Title="洪七公徒弟"}, new CustomerTitle{ Name="黄蓉", Title="才女"}, new CustomerTitle{ Name="黄蓉", Title="丐帮帮主"} }; //根据姓名进行内部联接 Console.WriteLine("内部联接"); var query = from customer in clist join title in titleList on customer.Name equals title.Name select new { Name = customer.Name, Age = customer.Age, Title = title.Title }; foreach (var ci in query) { Console.WriteLine("姓名:{0} 年龄:{1} {2}", ci.Name, ci.Age, ci.Title); } //根据姓名进行分组联接 Console.WriteLine("\n根据姓名进行分组联接"); var query2 = from customer in clist join title in titleList on customer.Name equals title.Name into tgroup select new { Name = customer.Name, Titles = tgroup }; foreach (var g in query2) { Console.WriteLine(g.Name); foreach (var g2 in g.Titles) { Console.WriteLine(" {0}", g2.Title); } } //根据姓名进行 左外部联接 Console.WriteLine("\n左外部联接"); var query3 = from customer in clist join title in titleList on customer.Name equals title.Name into tgroup from subTitle in tgroup.DefaultIfEmpty() select new { Name = customer.Name, Title = (subTitle == null ? "空缺" : subTitle.Title) }; foreach (var ci in query3) { Console.WriteLine("姓名:{0} {1} ", ci.Name, ci.Title); }
要仔细理解上例的,内联接,分组联接,以及左联接。
内部联接 姓名:欧阳晓晓 年龄:35 歌手 姓名:郭靖 年龄:17 大侠 姓名:郭靖 年龄:17 洪七公徒弟 姓名:黄蓉 年龄:17 才女 姓名:黄蓉 年龄:17 丐帮帮主 根据姓名进行分组联接 欧阳晓晓 歌手 上官飘飘 郭靖 大侠 洪七公徒弟 黄蓉 才女 丐帮帮主 左外部联接 姓名:欧阳晓晓 歌手 姓名:上官飘飘 空缺 姓名:郭靖 大侠 姓名:郭靖 洪七公徒弟 姓名:黄蓉 才女 姓名:黄蓉 丐帮帮主
LINQ to Objects是 LINQ的基础,而 LINQ to SQL、 LINQ to XML是中间 LINQ提供程序,他们主要是把数据源转换成 LINQ to Objects兼容的类型,以便 LINQ to Objects进行操做。 LINQ to Objects就是直接对IEnumerable或泛型IEnumerable<T>集合进行查询。LINQ表达式是LINQ标准查询运算符的一部分,而LINQ标准查询运算符则是LINQ to Objects的基础。它们是一组静态方法,被定义在System.Linq.Enumerable和System.Linq.Queryable类中。这两个类中方法基本一致,惟一的不一样点是System.Linq.Queryable类中方法会把LINQ表达式拆解成表达式目录树,其余一些Linq提供程序能够将这个表达式目录树翻译成查询语句,好比SQL语句,而后再执行相关操做。
本文主要学习System.Linq.Enumerable的扩展方法,这些方法按照执行的行为不一样,能够分为延期执行和当即执行。延期执行的运算符在枚举时被执行,下面要学习的就是延期执行方法的一部分。
1,Take 方法
Take方法用于从一个序列的开头返回指定数量的元素。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; //直接输出前3个元素 Console.WriteLine("Take方法直接输出前3个元素"); foreach (var name in names.Take(3)) { Console.WriteLine(name); } var query = from n in names where n.Length == 2 select n; Console.WriteLine("\nTake方法输出查询结果的前1个元素"); foreach (var s in query.Take(1)) { Console.WriteLine(s); }
输出结果:
Take方法直接输出前3个元素
郭靖
李莫愁
欧阳晓晓
Take方法输出查询结果的前1个元素
郭靖
2,TakeWhile 方法
TakeWhile方法获取序列中从开头起符合条件的元素,直到遇到不符合条件的元素为止的全部元素。条件代理部分有两种形式:
Func<TSource, bool> predicate
Func<TSource, int, bool> predicate 第二个参数是元素的索引
注意:当条件为假时,就中止了,后面的元素不会输出。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; //输出名字小于4个字的元素 var takeNames = names.TakeWhile(n => n.Length < 4); foreach (var name in takeNames) { Console.WriteLine(name); } Console.WriteLine("\nTakeWhile 带索引参数"); //输出名字字数小于等于4 而且索引小于4的元素 foreach (var name in names.TakeWhile((n, i) => n.Length <= 4 && i < 4)) { Console.WriteLine(name); }
输出结果:
郭靖
李莫愁
TakeWhile 带索引参数
郭靖
李莫愁
欧阳晓晓
黄蓉
3,Skip 方法
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; //跳过前3个元素 Console.WriteLine("Take方法跳过前3个元素"); foreach (var name in names.Skip(3)) { Console.WriteLine(name); } var query = from n in names where n.Length == 2 select n; Console.WriteLine("\nTake方法跳过查询结果的前1个元素"); foreach (var s in query.Skip(1)) { Console.WriteLine(s); }
输出结果:
Take方法跳过前3个元素
黄蓉
黄药师
Take方法跳过查询结果的前1个元素
黄蓉
4,SkipWhile 方法
SkipWhile 方法用于只要知足指定的条件,就跳过序列中得元素。
注意:当遇到条件为假时,就中止跳越了,输出剩余的全部元素。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; Console.WriteLine("SkipWhile跳过名字为2个字的元素"); foreach (var name in names.SkipWhile(n => n.Length == 2)) { Console.WriteLine(name); } Console.WriteLine("\nSkipWhile跳过名字小于4个字,而且索引小于2"); foreach (var s in names.SkipWhile((n, i) => n.Length < 4 && i < 2)) { Console.WriteLine(s); }
输出结果:
SkipWhile跳过名字为2个字的元素
李莫愁
欧阳晓晓
黄蓉
黄药师
SkipWhile跳过名字小于4个字,而且索引小于2
欧阳晓晓
黄蓉
黄药师
5,Reverse 方法
Reverse 方法用于反转序列中的元素。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; foreach (var name in names.Reverse()) { Console.WriteLine(name); }
输出结果:
黄药师
黄蓉
欧阳晓晓
李莫愁
郭靖
6,Distinct 方法
Distinct 方法用于去除重复元素。
string[] names = { "郭靖", "郭靖", "李莫愁", "欧阳晓晓", "欧阳晓晓", "黄蓉", "黄药师" }; Console.WriteLine("含有重复元素的数组"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重复元素的数组"); foreach (var name in names.Distinct()) { Console.Write(name + " "); }
输出结果:
含有重复元素的数组
郭靖 郭靖 李莫愁 欧阳晓晓 欧阳晓晓 黄蓉 黄药师
去除重复元素的数组
郭靖 李莫愁 欧阳晓晓 黄蓉 黄药师
自定义IEqualityComparer<T>接口的相等比较器
public class MyEqualityComparer<T> : IEqualityComparer<T> { #region IEqualityComparer<T> 成员 public bool Equals(T x, T y) { string temp = x as string; if (temp != null) { if (temp == "欧阳晓晓") //对"欧阳晓晓"不过滤 return false; } if (x.GetHashCode() == y.GetHashCode()) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } #endregion } private void DistinctDemo() { string[] names = { "郭靖", "郭靖", "李莫愁", "欧阳晓晓", "欧阳晓晓", "黄蓉", "黄药师" }; Console.WriteLine("含有重复元素的数组"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重复元素的数组,实现自定义IEqualityComparer<T>"); foreach (var name in names.Distinct(new MyEqualityComparer<string>())) { Console.Write(name + " "); } }
输出结果:
含有重复元素的数组
郭靖 郭靖 李莫愁 欧阳晓晓 欧阳晓晓 黄蓉 黄药师
去除重复元素的数组,实现自定义IEqualityComparer<T> 郭靖 李莫愁 欧阳晓晓 欧阳晓晓 黄蓉 黄药师
7,Union 方法
Union 方法 用于合并两个序列,并去掉重复元素。
string[] names = { "郭靖", "郭靖", "李莫愁", "欧阳晓晓", "欧阳晓晓", "黄蓉", "黄药师" }; Console.WriteLine("含有重复元素的数组"); foreach (var name in names) { Console.Write(name + " "); } Console.WriteLine("\n\n去除重复元素的数组,实现自定义IEqualityComparer<T>"); foreach (var name in names.Distinct(new MyEqualityComparer<string>())) { Console.Write(name + " "); }
输出结果:
合并后的元素
郭靖 李莫愁 欧阳晓晓 黄蓉 黄药师 杨过
自定义IEqualityComparer<T>接口的相等比较器
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("合并后的元素"); foreach (var name in names.Union(names2,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
输出结果:
合并后的元素
郭靖 李莫愁 欧阳晓晓 黄蓉 黄药师 杨过 欧阳晓晓
8,Concat 方法
Concat 方法 用于链接两个序列,与Union不一样,它不会过滤重复的元素。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("合并后的元素"); foreach (var name in names.Concat(names2)) { Console.Write(name + " "); }
输出元素:
合并后的元素
郭靖 李莫愁 欧阳晓晓 黄蓉 黄药师 郭靖 杨过 欧阳晓晓
9,Intersect 方法
Intersect 方法用于生成两个序列的交集。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("相交的元素"); foreach (var name in names.Intersect(names2)) { Console.Write(name + " "); }
输出结果:
相交的元素
郭靖 欧阳晓晓
自定义IEqualityComparer<T>
public class MyEqualityComparer<T> : IEqualityComparer<T> { #region IEqualityComparer<T> 成员 public bool Equals(T x, T y) { string temp = x as string; if (temp != null) { if (temp == "欧阳晓晓") //对"欧阳晓晓"不过滤 return false; } if (x.GetHashCode() == y.GetHashCode()) return true; else return false; } public int GetHashCode(T obj) { return obj.GetHashCode(); } #endregion }
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("相交的元素"); foreach (var name in names.Intersect(names2,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
输出结果:
相交的元素
郭靖
10,Except 方法
Except 方法用于生成两个序列的差集。
注意:返回是第一个数组里,去掉指定数组里的元素后,剩下的一个序列。
它和Intersect方法不是互补的,不要搞混了。下面的“杨过”就不会输出。由于它是指定数组里的元素,和源数组一毛钱关系都没有。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("2个数组的不一样元素"); foreach (var name in names.Except(names2)) { Console.Write(name + " "); }
输出结果:
2个数组的不一样元素
李莫愁 黄蓉 黄药师
运用自定义IEqualityComparer<T>指定比较器。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; string[] names2 = { "郭靖", "杨过", "欧阳晓晓" }; Console.WriteLine("2个数组的不一样元素"); foreach (var name in names2.Except(names,new MyEqualityComparer<string>())) { Console.Write(name + " "); }
输出结果:
2个数组的不一样元素
杨过 欧阳晓晓
11,Range 方法
Range 方法用于生成指定范围的整数序列。在BS程序中,常常须要分页显示,在页面中须要显示页面号码的连接,用这个方法能够生成页码的数组。
因为没有this关键字,它是一个普通的静态方法。
int istart = 1;//起始页码 int iend = 12; //结束页码 var pages = Enumerable.Range(1, iend - istart + 1); Console.WriteLine("输出页码"); foreach (var n in pages) { Console.Write("[{0}] ", n); }
输出结果:
输出页码
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]
12,Repeat 方法
Repeat 方法用于生成指定数量重复元素的序列。因为没有this关键字,它是一个普通的静态方法。
var people = new { Name = "郭靖", Age = 35 };//定义一个匿名类型 var peoples = Enumerable.Repeat(people, 4); Console.WriteLine("包含4个匿名元素:"); foreach (var n in peoples) { Console.WriteLine("{0} {1} ", n.Name, n.Age); }
输出结果:
包含4个匿名元素:
郭靖 35 郭靖 35 郭靖 35 郭靖 35
13,Empty 方法
Empty 方法用于获取一个指定类型参数的空序列。因为没有this关键字,它是一个普通的静态方法。
var s = Enumerable.Empty<string>(); Console.WriteLine("序列的元素数:{0} ", s.Count());
输出结果:
序列的元素数:0
14,DefaultIfEmpty 方法
DefaultIfEmpty 方法用于获取序列,若是序列为空则添加一个类型的默认值。例如:若是元素为引用类型,添加null元素;元素为int类型,则添加int的默认值0。
string[] names = { "郭靖", "李莫愁", "欧阳晓晓", "黄蓉", "黄药师" }; var intempty = Enumerable.Empty<int>();//空的Int类型序列 //没有找到元素的序列 var empty = from n in names where n.Length == 5 select n; Console.WriteLine("DefaultIfEmpty 返回有内容的序列"); foreach (var n in names) { Console.Write("{0} ", n); } Console.WriteLine("\nempty空序列元素数:{0}", empty.Count()); Console.WriteLine("empty空序列应用DefaultIfEmpty 后的元素数:{0}", empty.DefaultIfEmpty().Count()); Console.Write("empty空序列应用DefaultIfEmpty 后的元素值:"); foreach (var n in empty.DefaultIfEmpty()) { if (n == null) Console.Write("null"); } Console.WriteLine("\n****************************************"); Console.WriteLine("intempty空序列元素数:{0}", intempty.Count()); Console.WriteLine("intempty空序列应用DefaultIfEmpty 后的元素数:{0}", intempty.DefaultIfEmpty().Count()); Console.Write("intempty空序列应用DefaultIfEmpty 后的元素值:"); foreach (var n in intempty.DefaultIfEmpty()) { Console.Write(n); }
输出结果:
DefaultIfEmpty 返回有内容的序列
郭靖 李莫愁 欧阳晓晓 黄蓉 黄药师
empty空序列元素数:0 empty空序列应用DefaultIfEmpty 后的元素数:1 empty空序列应用DefaultIfEmpty 后的元素值:null **************************************** intempty空序列元素数:0 intempty空序列应用DefaultIfEmpty 后的元素数:1 intempty空序列应用DefaultIfEmpty 后的元素值:0
这个方法还能够指定一个自定义的默认值。
var intempty = Enumerable.Empty<int>();//空的Int类型序列 Console.Write("int 类型自定义默认值:"); foreach (var i in intempty.DefaultIfEmpty(200)) { Console.Write(i); }
输出结果:
int 类型自定义默认值:200
七、Cast 方法
Cast 方法用于按照TResult类型转换IEnumerable序列的集合。
//ArrayList没有实现IEnumerable<T>接口 ArrayList names = new ArrayList(); names.Add("郭靖"); names.Add("李莫愁"); names.Add("欧阳晓晓"); IEnumerable<string> newNames = names.Cast<string>(); foreach (var s in newNames) { Console.WriteLine(s); }
输出结果:
郭靖
李莫愁
欧阳晓晓
8,OfType 方法
OfType 方法用于根据TResult类型筛选IEnumerable类型序列的元素。它的用途和Cast方法相似,但OfType方法若是遇到不能强制转换成TResutl的类型,会丢弃该元素,而不会出现运行错误。
//ArrayList没有实现IEnumerable<T>接口 ArrayList names = new ArrayList(); names.Add("郭靖"); names.Add("李莫愁"); names.Add(100); names.Add(new Stack()); names.Add("欧阳晓晓"); IEnumerable<string> newNames = names.OfType<string>(); foreach (var s in newNames) { Console.WriteLine(s); }
输出结果:
郭靖
李莫愁
欧阳晓晓
9,AsEnumerable方法
AsEnumerable方法根据元素的类型转换为泛型IEnumerable<T>类型。
MSDN上的一个例子,AsEnumerable用于隐藏本身定义的和IEnumerable里的扩展方法同名的方法。
public class MyList<T> : List<T> { public IEnumerable<T> Where(Func<T, bool> predicate) { Console.WriteLine("In MyList of Where"); return Enumerable.Where(this, predicate); } } private void AsEnumerableDemo() { MyList<string> list = new MyList<string>() { "郭靖", "黄蓉", "黄药师" }; var query1 = list.Where(n => n.Contains("郭")); Console.WriteLine("query1 created"); var query2 = list.AsEnumerable().Where(n => n.Contains("郭")); Console.WriteLine("query2 created"); }
运行结果:
In MyList of Where
query1 created
query2 created
AsEnumerable方法常常用于Linq To SQL查询,将IQueryable<T>转换成IEnumerable<T>接口。由于一些扩展方法,如Reverse等,虽然在IQueryable<T>里有定义,但并不能将其翻译成对应的SQL语句,因此运行时会报错。用AsEnumerable方法转换成IEnumerable<T>后,实际的数据就在内存中操做。关于IQueryable<T>调用AsEnumerable背后的转换本质,有待进一步考证。
在应用到IEnumberable 和IQueryable两个接口时,代码每每很类似,从而形成了不少困惑,而后事实上他们两是有很大的区别的,各类都有本身特定的使用场景。下面是IEnumberable和IQueryable的属性对比:
IEnumerable | IQueryable | |
Namespace | System.Collections Namespace | System.Linq Namespace |
继承于 | No base interface | 继承于 IEnumerable |
Deferred Execution | 支持 | 支持 |
Lazy Loading | 不支持 | 支持 |
如何工做 | 当从数据库中查询数据,IEnumberable在服务器端执行查询操做,下载数据到客户端的内存中,而后再筛选数据,所以这个操做须要更多的工做而变得缓慢。 | 当从数据库中查询数据,IQueryable在服务器端根据全部的filter条件执行查询操做,所以该操做须要更少的工做而运行快。 |
适用于 | LINQ to Object and LINQ to XML queries. | LINQ to SQL queries. |
自定义查询 | 不支持 | 支持使用CreateQuery 和Execute 方法。 |
Extension mehtod parameter |
Extension methods supported in IEnumerable takes functional objects. | Extension methods supported in IEnumerable takes expression objects i.e. expression tree. |
使用场合 | 当从内存中的数据集合(如LIst,Array etc)查询数据的时候 | 当查询非内存中的数据集合(如远程数据库,service等)时。 |
最常使用 | 内存遍历 | Paging |
参考资料:http://www.cnblogs.com/xiashengwang/archive/2012/07/29/2613940.html