本文主要是梳理下Linq知识点,分为两部分,第一部分是收集网上关于Linq比较好的文章,第二部分写下自己的理解
一、推荐比较高的Linq文章
1、Linq之旅:Linq入门详解(Linq to Objects)
2、30分钟LINQ教程
3、[C#] 走进 LINQ 的世界
4、LINQ 图解
5、Linq表达式、Lambda表达式你更喜欢哪个?
6、LINQ之路系列博客导航
二、Linq相关的技术点梳理
1.隐式类型
(1)简介:在隐式类型出现之前,我们在声明一个变量的时候,总是要为一个变量指定他的类型。甚至在foreach一个集合的时候,也要为遍历的集合的元素,指定变量的类型,隐式类型的出现,大大减轻了程序员的压力。
(2)原理:隐式类型,使用var关键字创建,C#编译器会根据用于初始化局部变量的初始值推断出变量的数据类型,然后再转化成相应的数据类型。
(3)缺点:第一方面C#编译器推断出变量类型并进行转换,编译后成为IL代码,转化会消耗性能,但是很小,基本可以忽略不计。另一方面,使用var会让你遗忘“被封装类库”方法的返回值类型--有损可读性。尽量能用具体类型的地方尽量不要用var关键字。
(4)限制:第一方面,隐式类型只能应用于方法或者属性内局部变量的声明,不能使用var来定义返回值、参数的类型或类型的数据成员。另一方面, 使用var进行声明的局部变量必须赋初始值,并且不能以null作为初始值。
(5)示例:
var a = 1; //int a = 1; var b = "123";//string b = "123"; var myObj = new MyObj();//MyObj myObj = new MyObj()
2.匿名类型
(1)简介:匿名类型,只是一个继承了Object的、没有名称的类。C#编译器会在编译时自动生成名称唯一的类创建一个对象。如下代码:new关键字之后就直接为对象定义了属性,并且为这些属性赋值。对象创建出来之后,在创建对象的方法中,还可以畅通无阻的访问对象的属性,当把一个对象的属性拷贝到匿名对象中时,可以不用显示的指定属性的名字,这时原始属性的名字会被“拷贝”到匿名对象中
var obj = new {Guid.Empty, myTitle = "匿名类型", myOtherParam = new int[] { 1, 2, 3, 4 } }; Console.WriteLine(obj.Empty);//另一个对象的属性名字,被原封不动的拷贝到匿名对象中来了。
Console.WriteLine(obj.myTitle); Console.ReadKey();
(2)注意:如果你监视变量obj,你会发现obj的类型是Anonymous Type类型的,不要试图在创建匿名对象的方法外面去访问对象的属性!
(3)优点:这个特性在网站开发中,序列化和反序列化JSON对象时很有用
3.初始化器
(1)简介: 对象初始化器,提供一种非常简洁的方式来创建对象和为对象的属性赋值(相关还有“集合初始化器”)。我们创建一个对象并给对象的属性赋值,代码一般写成下面的样子
var myObj = new MyObj(); myObj.id = Guid.NewGuid(); myObj.Title = "allen";
自C#3.0引入了对象初始化器,代码可以写成如下的样子
var myObj1 = new MyObj() { id = Guid.NewGuid(), Title = "allen" };
集合初始化器的样例代码如下:
var arr = new List<int>() { 1, 2, 3, 4, 5, 6 };
4.自动属性
(1)简介:为一个类型定义属性,我们一般都写如下的代码:
public class MyObj2 { private Guid _id; private string _Title; public Guid id { get { return _id; } set { _id = value; } } public string Title { get { return _Title; } set { _Title = value; } } }
但很多时候,这些私有变量对我们一点用处也没有,比如对象关系映射中的实体类。自C#3.0引入了自动实现的属性,以上代码可以写成如下形式:
public class MyObj { public Guid id { get; set; } public string Title { get; set; } }
5. 委托
(1)简介:委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递。与其他的类不同,委托类具有一个签名,并且它只能对与其签名匹配的方法进行引用。
(2)示例:

using System; namespace DelegateDemo { class Program { // 声明一个委托类型,两个参数均为int类型,返回值为int类型 public delegate int Calc(int a, int b); // 定义和委托签名一致的方法 static int Add(int a, int b) { return a + b; } static void Main(string[] args) { Console.WriteLine("请输入第一个数:"); int a = int.Parse(Console.ReadLine()); Console.WriteLine("请输入第二个数:"); int b = int.Parse(Console.ReadLine()); // 定义一个Calc委托类型的变量,把和该委托签名一致的Add方法的引用赋值给变量 Calc method = Add; Console.WriteLine("加法运算:{0}+{1}={2}", a, b, method(a, b)); Console.ReadKey(); } } }
执行结果:
6.泛型
(1)为什么要有泛型?
假设你是一个方法的设计者,这个方法有一个传入参数,有一个返回值。但你并不知道这个参数和返回值是什么类型的,如果没有泛型,你可能把参数和返回值的类型都设定为Object了,这个方法的消费者,会把他的对象传进来(有可能会做一次装箱操作),并且得到一个Object的返回值,他再把这个返回值强制类型转化为他需要的类型,除了装箱和类型转化时的性能损耗外,代码工作的很好!那么这些性能损耗能避免掉吗?有泛型之后就可以了。
(2)使用简单的泛型示例:
var intList = new List<int>() { 1,2,3}; intList.Add(4); intList.Insert(0, 5); foreach (var item in intList) { Console.WriteLine(item); } Console.ReadKey();
(3)泛型类型示例:
List<T>、Dictionary<TKey, TValue>等泛型类型都是.net类库定义好并提供给我们使用的,但在实际开发中,我们也经常需要定义自己的泛型类型
public static class SomethingFactory<T> { public static T InitInstance(T inObj) { if (false)//你的判断条件 { //do what you want... return inObj; } return default(T); } }
这段代码的消费者如下:
var a1 = SomethingFactory<int>.InitInstance(12); Console.WriteLine(a1); Console.ReadKey();
输出的结果为0,这就是一个自定义的静态泛型类型,此类型中的静态方法InitInstance对传入的参数做了一个判断,如果条件成立,则对传入参数进行操作之后并把它返回,如果条件不成立,则返回一个空值
(4)注意:第一方面,传入参数必须为指定的类型,因为我们在使用这个泛型类型的时候,已经规定好它能接收什么类型的参数,但在设计这个泛型的时候,我们并不知道使用者将传递什么类型的参数进来。另一方面,如果你想返回T类型的空值,那么请用default(T)这种形式,因为你不知道T是值类型还是引用类型,所以别擅自用null。
(5)泛型约束:很多时候我们不希望使用者太过自由,我们希望他们在使用我们设计的泛型类型时,不要很随意的传入任何类型,对于泛型类型的设计者来说,要求使用者传入指定的类型是很有必要的,因为我们只有知道他传入了什么东西,才方便对这个东西做操作,让我们来给上面设计的泛型类型加一个泛型约束
public static class SomethingFactory<T> where T:MyObj
这样在使用SomethingFactory的时候就只能传入MyObj类型或MyObj的派生类型啦。注意:还可以写成这样where T:MyObj,new()来约束传入的类型必须有一个构造函数。
(6)泛型的好处:算法的重用;类型安全;提升性能;可读性更好
7.泛型委托
(1)简介:委托需要定义delgate类型,使用起来颇多不便,而且委托本就代表某一类方法,开发人员经常使用的委托基本可以归为以下三类:
(2)Func委托
Func委托,是微软为我们预定义的常用委托,封装一个具有:零个或多个指定类型的输入参数并返回一个指定类型的结果值的方法。Func是有返回值的泛型委托,可以没有参数,但最多只有16个参数,并且必须要有返回值,不能为void类型。
示例:

执行结果:
(3)Action泛型委托
Action是没有返回值的泛型委托,可以没有参数,但最多只有16个参数,返回值为void类型。

using System; namespace DelegateDemo { class Program { static void Add(int a, int b) { Console.WriteLine($"加法运算:{a}+{b}={a + b}"); } static void Main(string[] args) { Action<int, int> method = Add; method(12, 18); Console.ReadKey(); } } }
执行结果:
(4)Predicate泛型委托
Predicate是只有一个参数,且返回值为bool类型的泛型委托。

using System; namespace DelegateDemo { class Program { static void Main(string[] args) { Predicate<int> method = (x) => { return x > 10; }; Console.WriteLine(method(12)); Console.ReadKey(); } } }
执行结果:
8.匿名方法
(1)示例:将上述Action泛型委托改造成匿名方法
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { Action<int, int> method = (a, b) => { Console.WriteLine($"加法运算:{a}+{b}={a + b}"); }; method(12, 18); Console.ReadKey(); } } }
执行结果:
9.Lambda表达式
(1)简介:.net的设计者发现在使用匿名方法时,仍旧有一些多余的字母或单词的编码工作,比如delegate关键字,于是进一步简化了匿名方法的写法。Lambda表达式只是用更简单的方式来书写匿名方法,从而彻底简化.NET委托类型的使用。 Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。
(2)示例:
using System; namespace DelegateDemo { class Program { static void Main(string[] args) { // 匿名方法 Func<string, string, string> func1 = delegate (string a, string b) { return $"{a}欢迎{b}"; }; Console.WriteLine(func1("北京", "qxh")); // Lambda表达式 Func<string, string, string> func2 = (a, b) => { return $"{a}欢迎{b}"; }; Console.WriteLine(func2("北京", "qxh")); Console.ReadKey(); } } }
上述代码中,使用的匿名方法的代码如下:
delegate (string a, string b) { return $"{a}欢迎{b}"; };
使用lambda表达式的代码如下:
Func<string, string, string> func2 = (a, b) => { return $"{a}欢迎{b}"; };
可以看出Lambda表达式更简洁。
10.扩展方法
(1)简介:如果想给一个类型增加行为,一定要通过继承的方式实现吗?不一定的!我们可以使用扩展方法
(2)注意:
扩展方法必须在一个非嵌套、非泛型的静态类中定义,是一个静态方法,扩展方法至少要有一个参数,其第一个参数需要使用this关键字标识,指示它所扩展的类型。第一个参数不能有其他修饰符(比如ref或者out),第一个参数不能是指针类型
扩展方法可以将方法写入最初没有提供该方法的类中。还可以把方法添加到实现某个接口的任何类中,这样多个类就可以使用相同的实现代码。(LINQ中,System.Linq.Queryable.cs和System.Linq.Enumerable.cs 正是对接口添加扩展方法)
扩展方法虽定义为一个静态方法,但其调用时不必提供定义静态方法的类名,只需引入对应的命名空间,访问方式同实例方法。
扩展方法不能访问它所扩展的类型的私有成员。
(3)示例:

using System; namespace DelegateDemo { class Program { static void Main(string[] args) { var a = "北京欢迎您"; a.PrintString(); Console.ReadKey(); } } public static class ExtentClass { public static void PrintString(this String val) { Console.WriteLine(val); } } }
执行结果:
11.迭代器
(1)简介:我们每次针对集合类型编写foreach代码块,都是在使用迭代器,这些集合类型都实现了IEnumerable接口,都有一个GetEnumerator方法,但对于数组类型就不是这样,编译器把针对数组类型的foreach代码块,替换成了for代码块。
(2)迭代器的优点:假设我们需要遍历一个庞大的集合,只要集合中的某一个元素满足条件,就完成了任务,你认为需要把这个庞大的集合全部加载到内存中来吗?,当然不用(C#3.0之后就不用了)!
(3)示例:

using System; using System.Collections.Generic; namespace DelegateDemo { class Program { static void Main(string[] args) { foreach (var i in GetIterator()) { if (i == 2) { break; } Console.WriteLine(i); } Console.ReadKey(); } static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield return 2; Console.WriteLine("迭代器返回了3"); yield return 3; } } }
执行结果为:
可以看到:当迭代器返回2之后,foreach就退出了,并没有输出“迭代器返回了3”,也就是说下面的工作没有做。
(4)yield 关键字
在迭代器块中用于向枚举数对象提供值或发出迭代结束信号。也就是说我们可以在生成迭代器的时候,来确定什么时候终结迭代逻辑,上面的代码可以改成如下形式:

static IEnumerable<int> GetIterator() { Console.WriteLine("迭代器返回了1"); yield return 1; Console.WriteLine("迭代器返回了2"); yield break; Console.WriteLine("迭代器返回了3"); yield return 3; }
(5)注意事项
做foreach循环时多考虑线程安全性,在foreach时不要试图对被遍历的集合进行remove和add等操作,任何集合即使被标记为线程安全的,在foreach的时候,增加项和移除项的操作都会导致异常
IEnumerable接口是LINQ特性的核心接口,只有实现了IEnumerable接口的集合,才能执行相关的LINQ操作,比如select,where等
三、Linq介绍
从这幅图中,我们可以知道LINQ包括五个部分:LINQ to Objects、LINQ to XML、LINQ to SQL、LINQ to DataSet、LINQ to Entities。
|
程序集 |
命名空间 |
描述 |
LINQ to Objects |
System.Core.dll |
System.Linq |
提供对内存中集合操作的支持 |
LINQ to XML |
System.Xml.Linq.dll |
System.Xml.Linq |
提供对XML数据源的操作的支持 |
LINQ to SQL |
System.Data.Linq.dll |
System.Data.Linq |
提供对Sql Server数据源操作的支持。(微软已宣布不再更新,推荐使用LINQ to Entities) |
LINQ to DataSet |
System.Data.DataSetExtensions.dll |
System.Data |
提供对离线数据操作的支持。 |
LINQ to Entities |
System.Core.dll 和 System.Data.Entity.dll |
System.Linq 和System.Data.Objects |
LINQ to Entities 是 Entity Framework 的一部分并且取代 LINQ to SQL 作为在数据库上使用 LINQ 的标准机制。(Entity Framework 是由微软发布的开源对象-关系映射(ORM)框架,支持多种数据库。) |
目前,还可以下载其他第三方提供程序,例如LINQ to JSON、LINQ to MySQL、LINQ to Amazon、LINQ to Flickr和LINQ to SharePoint。无论使用什么数据源,都可以通过LINQ使用相同的API进行操作。怎样区分LINQ操作时,使用的是哪个LINQ提供程序?LINQ提供程序的实现方案是:根据命名空间和第一个参数的类型来选择的。实现扩展方法的类的命名空间必须是打开的,否则扩展类就不在作用域内。Eg:在LINQ to Objects中定义的 Where() 方法参数和在 LINQ to Entities中定义的 Where() 方法实现是不同。
// LINQ to Objects: public static class Enumerable { public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, bool> predicate); } // LINQ to Entities public static class Queryable { public static IQueryable<TSource> Where<TSource>( this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate); }
标准查询运算符
操作符 | 类别 | 语义 | 示例 |
---|---|---|---|
Where | 筛选操作符(Restriction) | Where(Predicate) | Dim lowNums = numbers.Where(Function(num) num < 5) |
Select | 投影操作符(Projection) | Select(匿名函数) | Dim lowNums = numbers.Select(Function(num) num*num) |
SelectMany | 投影操作符(Projection) | 返回多行结果,用于多表的交叉连接(cross join) | Dim res = Employees.SelectMany(Function(e) e.Family.Select(Function(c)c.name)) |
Skip | 分块操作符(Partitioning) | 跳过前n个元素 | Dim res=data.Skip(4) |
SkipWhile | 分块操作符(Partitioning) | 跳过起始处使条件为真的所有元素 | Dim res=data.SkipWhile(Function(i) i%2 = 0) |
Take | 分块操作符(Partitioning) | 返回开头之处的n个元素 | Dim res=data.Take(3) |
TakeWhile | 分块操作符(Partitioning) | 返回起始处使条件为真的所有元素 | Dim res=data.TakeWhile(Function(i) i%3 = 0) |
Join | 连接操作符 | 内连接两个或多个表,仅限于Equals运算符 | from sup in suppliers join cust in customers on sup.Country equals cust.Country |
GroupJoin | 连接操作符 | 类似于LEFT OUTER JOIN,右侧集合匹配于左侧集合键值的元素被分组 | From cust In customers Group Join ord In orders On cust.CustomerID Equals ord.CustomerID Into CustomerOrders = Group, OrderTotal = Sum(ord.Total) |
Concate | 合并操作符 | 用于连接两个序列 | returnValue = firstSeq.Concat(secondSeq) |
OrderBy | 排序操作符(Ordering) | Dim query As IEnumerable(Of Person) = Persons.OrderBy(Function(p) p.Age) | |
OrderByDescending | 排序操作符(Ordering) | ||
ThenBy | 排序操作符(Ordering) | ||
ThenByDescending | 排序操作符(Ordering) | ||
Reverse | 排序操作符(Ordering) | ||
GroupBy | 分组操作符 | Dim numbers = {5, 4, 1, 3, 9, 8, 6, 7, 2, 0} Dim numberGroups = From num In numbers Group num By remainder5 = (num Mod 5) Into numGroup = Group Select New With {.Remainder = remainder5, .numbers = numGroup} |
|
Distinct | 集合操作符 | 去重复 | |
Union | 集合操作符 | 集合并 | |
Intersect | 集合操作符 | 集合交 | |
Except | 集合操作符 | 集合差 | |
AsEnumerable | 转换操作符 | 用于把一个IEnumerable的派生类型转化为IEnumerable类型 | |
AsQueryable | 转换操作符 | IEnumerable(Of T)转化为IQueryable(Of T). | |
ToArray | 转换操作符 | 转换为数组 | |
ToList | 转换操作符 | 转换为List | |
ToDictionary | 转换操作符 | 转换为一对一的字典(键-值对的集合) | |
ToLookup | 转换操作符 | 转换为一对多的字典(键-值集的集合) | |
OfType | 转换操作符 | 获取指定类型的元素组成一个数组 | object[] numbers = { null, 1.0, "two", 3, "four", 5, "six", 7.0 }; var doubles = numbers.OfType<double>(); |
Cast | 转换操作符 | 把序列的所有元素转换为指定类型 | |
SequenceEqual | 相等操作符 | 两个序列的元素依次相同返回真。使用元素所属类的IEqualityComparer(Of T) 泛型界面做相等比较 | |
First | 元素操作符 | 返回序列第一个元素(或满足条件第一个元素),没有则异常 | |
FirstOrDefault | 元素操作符 | 返回序列第一个元素,没有则返回空或默认值 | |
Last | 元素操作符 | 返回序列最后一个元素,没有则异常 | |
LastOrDefault | 元素操作符 | 返回序列最后一个元素,没有则返回空或默认值 | |
Single | 元素操作符 | 返回序列唯一元素,如果没有元素或多个元素则异常 | |
SingleOrDefault | 元素操作符 | 返回序列唯一元素,如果没有元素或多个元素则空或默认值 | |
ElementAt | 元素操作符 | 返回序列指定元素,失败则异常 | |
ElementAtOrDefault | 元素操作符 | 返回序列指定元素,失败则空或默认值 | |
DefaultIfEmpty | 元素操作符 | 返回序列,如果序列为空则返回元素的默认值 | For Each number As Integer In numbers.DefaultIfEmpty()〈br> output.AppendLine(number) Next |
All | 序列元素数量操作符 | 序列所有元素满足条件则为真 | |
Any | 序列元素数量操作符 | 序列有一个元素满足条件则为真 | |
Contains | 序列元素数量操作符 | 是否包含一个元素 | |
Count | 统计操作符 | 计数,可选一个谓词 | int oddNumbers = numbers.Count(n => n% 2 == 1); |
LongCount | 统计操作符 | 计数,返回Int64类型 | |
Sum | 统计操作符 | 求和,可选对一个lambda函数表达式 | double totalChars = words.Sum(w => w.Length); |
Min | 统计操作符 | 最小值,可选对一个lambda函数表达式 | int shortestWord = words.Min(w => w.Length); |
Max | 统计操作符 | ||
Average | 统计操作符 | ||
Aggregate | 统计操作符 | 参数为一个委托,在序列的每个元素上执行该委托。委托的第一个参数为当前累计值,第二个参数为当前元素,返回值为新的累计值 | Dim reversed As String = words.Aggregate(Function(ByVal current, ByVal word) word & " " & current) |
equals/Equals | 关键字 | 用于Join子句 | |
from/From | 关键字 | ||
in/In | 关键字 | 指出数据源 | |
into/Into | 关键字 | 用于Group By子句 | |
key | 关键字 | 用于Group By子句的无名类型 | |
let | 关键字 | 给表达式定义别名 | From prod In products Let Discount = prod.UnitPrice * 0.1 Where Discount >= 50 Select prod.ProductName, prod.UnitPrice, Discount |
Group | 关键字 | 在GroupBy子句的Into中用于辨识分组结果 | From num In numbers Group num By remainder5 = (num Mod 5) Into Group |
Range | 方法 | 产生一个整数序列 | From n In Enumerable.Range(100, 50) |
Repeat | 方法 | 产生一个整数序列 | From n In Enumerable.Repeat(7, 10) |
四、LINQ to Objects
先进行数据源配置

using System.Collections.Generic; namespace LinqToObjectDemo { /// <summary> /// 学生 /// </summary> public class Student { /// <summary> /// 姓名 /// </summary> public string Name { get; set; } /// <summary> /// 年龄 /// </summary> public int Age { get; set; } /// <summary> /// 班级名称 /// </summary> public string ClassName { get; set; } } public class Class { /// <summary> /// 班级名称 /// </summary> public string Name { get; set; } /// <summary> /// 班主任 /// </summary> public string HeadMaster { get; set; } } /// <summary> /// 数据源 /// </summary> public static class DataSource { /// <summary> /// 获取学生数据源 /// </summary> /// <returns></returns> public static IList<Student> GetStudents() { List<Student> stuList = new List<Student>(10); stuList.Add(new Student { Name = "s1", Age = 1, ClassName = "c1" }); stuList.Add(new Student { Name = "s2", Age = 2, ClassName = "c1" }); stuList.Add(new Student { Name = "s3", Age = 3, ClassName = "c1" }); stuList.Add(new Student { Name = "s4", Age = 4, ClassName = "c3" }); stuList.Add(new Student { Name = "s5", Age = 5, ClassName = "c1" }); stuList.Add(new Student { Name = "s6", Age = 6, ClassName = "c1" }); stuList.Add(new Student { Name = "s7", Age = 7, ClassName = "c2" }); stuList.Add(new Student { Name = "s8", Age = 8, ClassName = "c2" }); stuList.Add(new Student { Name = "s9", Age = 9, ClassName = "c2" }); stuList.Add(new Student { Name = "s10", Age = 10, ClassName = "c3" }); return stuList; } /// <summary> /// 获取班级数据源 /// </summary> /// <returns></returns> public static List<Class> GetClasss() { List<Class> classeList = new List<Class>(3); classeList.Add(new Class { Name = "c1", HeadMaster = "h1" }); classeList.Add(new Class { Name = "c2", HeadMaster = "h2" }); classeList.Add(new Class { Name = "c3", HeadMaster = "h3" }); return classeList; } } }
根据条件返回匹配元素的集合IEnumerable<T>。
(1) Where:根据返回bool值的Func委托参数过滤元素。业务说明:查询获得年龄大于3且是c1班级的学生
/// <summary> /// 查询获得年龄大于3且是c1班级的学生 /// </summary> private static void Where_Query() { var stuList = from s in DataSource.GetStudents() where s.Age > 3 && s.ClassName == "c1" select s; foreach (var stu in stuList) { Console.WriteLine($"年龄:{stu.Age}; 姓名:{stu.Name};所在班级:{stu.ClassName}"); } }
(2) OfType<TResult>:接收一个非泛型的IEnumerable集合,根据OfType泛型类型参数过滤元素,只返回TResult类型的元素。业务说明:过滤object数组中的元素,返回字符串类型的数组。
object[] data = { "one", 2, 3, "four", "five", 6 }; var query = data.OfType<string>(); // "one", "four", "five"
执行结果:
(3)Distinct:删除序列中重复的元素。
int[] ages = new int[] { 1, 2, 3, 4, 1, 2 }; var result = ages.Distinct(); foreach (var item in result) { Console.WriteLine(item); }
执行结果:
2、投影操作符
(1)Select 将序列的每个元素经过lambda表达式处理后投影到一个新类型元素上。(与SelectMany不同在于,若单个元素投影到IEnumerable<TResult>,Select不会对多个IEnumerable<TResult>进行合并)
var stuList = from s in DataSource.GetStudents() where s.Age > 3 && s.ClassName == "c1" select $"年龄:{s.Age}; 姓名:{s.Name};所在班级:{s.ClassName}"; foreach (var stu in stuList) { Console.WriteLine(stu); }
执行结果:
(1) SelectMany
List<int> arr = new List<int>() { 1, 2, 3, 4, 5, 6, 7 }; arr.SelectMany<int, string>(a => { return new List<string>() { "a", a.ToString() }; }); foreach (var item in arr) { Console.WriteLine(item); }
执行结果:说明:将序列的每个元素投影到一个序列中,最终把所有的序列合并
3、排序操作符
(1)OrderBy<TSource,TKey>,OrderByDescending<TSource,TKey>:根据指定键按升序或降序对集合进行第一次排序,输出IOrderedEnumerable<TSource>。
var stuList = from s in DataSource.GetStudents() .OrderBy(r=>r.Age) where s.Age > 3 && s.ClassName == "c1" select s; foreach (var stu in stuList) { Console.WriteLine(stu.Age); }
执行结果:
(2)ThenBy<TSource,TKey>,ThenByDescending<TSource,TKey>:只会对那些在前一次排序中拥有相同键值的elements重新根据指定键按升序或降序排序。输出IOrderedEnumerable <TSource>。
var stuList = from s in DataSource.GetStudents() .OrderBy(r=>r.Age) .ThenByDescending(r=>r.Name) where s.Age > 3 && s.ClassName == "c1" select s; foreach (var stu in stuList) { Console.WriteLine(stu.Age); }
执行结果:
(3)Reverse<TSource>:反转集合中所有元素的顺序。
List<int> list = new List<int> { 1, 2, 3, 4, 5, 6 }; list.Reverse(); foreach (var item in list) { Console.WriteLine(item); }
执行结果:
4、连接操作符
(1) Join:基于匹配键对两个序列的元素进行关联。join…on…equals…支持多个键关联
var stuList = from s in DataSource.GetStudents() join c in DataSource.GetClasss() on s.ClassName equals c.Name select new { ClassName = c.Name, StudentName = s.Name, Age = s.Age }; foreach (var stu in stuList) { Console.WriteLine($"班级:{stu.ClassName};学生{stu.StudentName}"); }
执行结果:
(2)into:基于键相等对两个序列的元素进行关联并对结果进行分组。常应用于返回“主键对象-外键对象集合”形式的查询。注意:直接出现在join子句之后的into关键字会被翻译为GroupJoin,而在select或group子句之后的into表示继续一个查询。
var stuList = from s in DataSource.GetStudents() join c in DataSource.GetClasss() on s.ClassName equals c.Name into g select new { ClassEntity = g, StudentName = s.Name, Age = s.Age }; foreach (var stu in stuList) { Console.WriteLine($"班级:{stu.ClassEntity.FirstOrDefault().Name};学生{stu.StudentName}"); }
执行结果:
5、分组操作符
(1)返回值为 IEnumerable<IGrouping<TKey, TSource>> ,根据指定的键选择器函数对序列中的元素进行分组。
var stuList = from s in DataSource.GetStudents() group s by s.Name into g select new { StuEntity = g }; foreach (var stu in stuList) { Console.WriteLine($"班级:{stu.StuEntity.FirstOrDefault().Name};学生{stu.StuEntity.FirstOrDefault().Age}"); }
执行结果:
6、量词操作符
如果元素序列满足指定的条件,量词操作符就返回布尔值。
(1) Any:确定序列是否包含任何元素;或确定序列中的任何元素是否都满足条件。
var result =DataSource.GetStudents().Any(p => p.Age == 3);
(2) All:确定序列中的所有元素是否满足条件。
(3) Contains:确定序列是否包含指定的元素。
7、分区操作符
添加在查询的“最后”,返回集合的一个子集。
(1)Take:从序列的开头返回指定数量的连续元素。
var result = DataSource.GetStudents().Take(10); foreach (var item in result) { Console.WriteLine(item.Name); }
执行结果:
(2)TakeWhile:只要满足指定的条件,就会返回序列的元素。
var result = DataSource.GetStudents().TakeWhile(p=>p.Name=="s1"); foreach (var item in result) { Console.WriteLine(item.Name); }
执行结果:
(3)Skip:跳过序列中指定数量的元素,然后返回剩余的元素。
var result = DataSource.GetStudents().Skip(5); foreach (var item in result) { Console.WriteLine(item.Name); }
执行结果:
(4)SkipWhile:只要满足指定的条件,就跳过序列中的元素,然后返回剩余元素。
var result = DataSource.GetStudents().SkipWhile(p=>p.Name=="s1"); foreach (var item in result) { Console.WriteLine(item.Name); }
执行结果:
8、集合操作符
(1)Union:并集,返回两个序列的并集,去掉重复元素。
(2)Concat:并集,返回两个序列的并集。
(3)Intersect:交集,返回两个序列中都有的元素,即交集。
(4)Except:差集,返回只出现在一个序列中的元素,即差集。
(5)Zip:通过使用指定的委托函数合并两个序列,集合的总个数不变。
(6) SequenceEqual:判断两个序列是否相等,需要内容及顺序都相等。
9、元素操作符
这些元素操作符仅返回一个元素,不是IEnumerable<TSource>。(默认值:值类型默认为0,引用类型默认为null)
(1) First:返回序列中的第一个元素;如果是空序列,此方法将引发异常。
(2) FirstOrDefault:返回序列中的第一个元素;如果是空序列,则返回默认值default(TSource)。
(3)Last:返回序列的最后一个元素;如果是空序列,此方法将引发异常。
(4)LastOrDefault:返回序列中的最后一个元素;如果是空序列,则返回默认值default(TSource)。
(5)Single:返回序列的唯一元素;如果是空序列或序列包含多个元素,此方法将引发异常。
(6)SingleOrDefault:返回序列中的唯一元素;如果是空序列,则返回默认值default(TSource);如果该序列包含多个元素,此方法将引发异常。
(7)ElementAt:返回序列中指定索引处的元素,索引从0开始;如果索引超出范围,此方法将引发异常。
(8) ElementAtOrDefault:返回序列中指定索引处的元素,索引从0开始;如果索引超出范围,则返回默认值default(TSource)。
10、合计操作符
(1)Count:返回一个 System.Int32,表示序列中的元素的总数量。
(2)LongCount:返回一个 System.Int64,表示序列中的元素的总数量。
(3)Sum:计算序列中元素值的总和。
(4)Max:返回序列中的最大值。
(5)Min:返回序列中的最小值。
(6)Average:计算序列的平均值。
(7)Aggregate:对序列应用累加器函数。
11、转换操作符
(1)Cast:将非泛型的 IEnumerable 集合元素转换为指定的泛型类型,若类型转换失败则抛出异常。
(2)ToArray:从 IEnumerable<T> 创建一个数组。
(3) ToList:从 IEnumerable<T> 创建一个 List<T>。
(4)ToDictionary:根据指定的键选择器函数,从 IEnumerable<T> 创建一个 Dictionary<TKey,TValue>。
(5)ToLookup:根据指定的键选择器函数,从 IEnumerable<T> 创建一个 System.Linq.Lookup<TKey,TElement>。
(6)DefaultIfEmpty:返回指定序列的元素;如果序列为空,则返回包含类型参数的默认值的单一元素集合。
(7) AsEnumerable:返回类型为 IEnumerable<T> 。用于处理LINQ to Entities操作远程数据源与本地集合的协作。
12、生成操作符
生成操作符返回一个新的集合。(三个生成操作符不是扩展方法,而是返回序列的正常静态方法)
(1) Empty:生成一个具有指定类型参数的空序列 IEnumerable<T>。
(2) Range:生成指定范围内的整数的序列 IEnumerable<Int32>。
(3) Repeat:生成包含一个重复值的序列 IEnumerable<T>。
五、LINQ to SQL
六、LINQ to Entity
(1)