C#中的Linq(Language Integrated Query)
1.Linq是什么?
- 官方定义
语言集成查询 (LINQ) 为 C# 和 Visual Basic 提供语言级查询功能和高阶函数 API,让你能够编写具有很高表达力度的声明性代码。
这听起来让人一头雾水,笔者个人的理解为将一些序列(主要是实现了IEnumerable的对象,但不仅仅是IEnumerable)通过一些规则投影为另外一些序列,当你要将一个IEnumerable对象中的数据做一些操作的时候,使用linq比使用传统的循环更加的高效和优雅。
2.作用对象和基本语法
2.1 常见的作用对象
- .NET的原生集合(List,Array,Dictionary...)
- XML文件
- 数据库(搭配ORM框架使用)
- JSON (搭配JSON解析包使用,Newtonsoft.Json)
2.2 查询表达式和链式表达式
Linq有两种常见的语法,即查询表达式(Query Expression)和链式表达式(Chained Expression),查询表达式类似于常见的SQL语言,而链式表达式更类似于不同API的连续调用,同时链式表达式也说明了Linq的本质,即"一系列的拓展方法(Extension members)"
C#
// 链式表达式 获取该字节数组中所有的偶数且排序
var r = new Random(1234);
var bytes = new byte[200];
r.NextBytes(bytes);
var result = bytes
.Where(x => x % 2 == 0)
.OrderBy(x => x);
result.Dump();
C#
// 查询表达式 获取该字节数组中所有的偶数且排序
var r = new Random(1234);
var bytes = new byte[200];
r.NextBytes(bytes);
var result =
from x in bytes
where x % 2 ==0
orderby x
select x;
result.Dump();
3.主要功能
3.1 筛选,排序,选择
3.1.1 筛选(Filtering)
用于从集合中过滤出符合条件的元素
- Where 最常用的筛选方法
C#
// 筛选出集合中年龄大于等于18岁的user对象
var res = users.Where(u => u.Age >= 18);
- OfType<> 筛选出符合类型的元素
C#
var set = new ArrayList() {1,2,3.14,true,"hello","string"};
var res = set.OfType<string>();
- Dinstinct 去重
C#
var list = new List<int>() {1,1,1,1,11,2,3,4,5,6};
var res = list.Distinct();
3.1.2 排序(Ordering)
用于改变集合中元素的排序
- OrderBy和OrderByDescending 升序和降序排列
C#
var users = new List<User>() {
new User(){Name = "alex",Age = 30},
new User(){Name = "bob",Age = 20},
new User(){Name = "john",Age = 18},
new User(){Name = "alice",Age = 25},
new User(){Name = "jack",Age = 27},
new User(){Name = "hans",Age = 11}
};
//按照客户年龄升序排列
var res1 = users.OrderBy(u => u.Age);
res1.Dump();
//按照客户年龄降序排列
var res2 = users.OrderByDescending(u => u.Age);
res2.Dump();
- ThenBy和ThenByDescending 在第一级排序的基础上进行次级排序
C#
// 当Age属性一致时,使用Number属性排序
var res2 = users
.OrderBy(u => u.Age)
.ThenBy(u => u.Number);
res2.Dump();
- Reverse 反转
C#
var res = users.Reverse();
3.1.3 选择(Selection/Projection)
- Select 将元素映射到新形式
C#
//只提取名字
var res = users.Select(u => u.name);
- SelectMany 用于"展平"集合(如果一个集合内包含更多的集合对象,可以把所有的子集合合并为一个大的序列) SyntaxEditor Code Snippet
C#
var sets = new int[][] {
new int[] {1,2,3,4,5},
new int[] {10,20,30,40,50},
new int[] {100,200,300,400,500}
};
var res = sets.SelectMany(x => x);
3.2 分组,聚合,合并
3.2.1 分组(Gruoping)
- GroupBy 将序列中的元素按照指定的键进行分组
C#
//按照Department属性分类 返回的是一个IEnumerable<Grouping<TKey,TElement>>
var res = users.GroupBy(u => u.Department);
3.2.2 聚合(Aggregation)
用于对集合进行计算,通常是将多个值通过计算转化为一个值。常见的聚合方法
- Count()计算元素个数
- Sum()计算总和
- Average()计算平均值
- Min()/Max()最小值和最大值
3.2.3 合并(Joining / Combining)
用于将两个不同的数据源关联起来,或将多个集合并为一个。
- Join 内连接(Inner Join),只保留两个集合都有匹配项的数据
C#
var res = students.Join(classes,s => s.ClassId,c => c.Id, (s,c) => new{s.Name,c.ClassName})
- GroupJoin 左外连接(Left Join)
- 集合操作
| 方法名 | 作用 |
|---|---|
| Concat() | 直接把第二个集合接在第一个后面 |
| Union() | 并集 |
| Intersect() | 交集 |
| Except() | 差集 |
4.延迟执行(Defer)和消耗(Exhaust)
4.1 延迟执行
只有当调用消耗方法(一些文档中也称为立即执行算子或终端算子)时,所书写的所有Linq才会被执行
C#
var arr = Enumerable.Range(0,5).Select(x => r.Next(300));
var res = arr.Select(
x => {
Thread.Sleep(1000);
return x * x;
}
);
//几乎立刻就输出了"Something"
"Something".Dump();
在执行上放的代码后Sleep并没有被执行
C#
var arr = Enumerable.Range(0,5).Select(x => r.Next(300));
var res = arr.Select(
x => {
Thread.Sleep(1000);
return x * x;
}
);
//等待了五秒才成功输出
res.Dump();
只有执行了Dump,上方的Linq才被执行。
4.2 消耗方法(Exhaust Method)
消耗方法,在一些文档中也被称为立即执行算子或终端算子,当你在Linq中写下Where或Select时,代码并没有被真正的执行,只是创建了一个对于的"查询计划",只有当执行消耗方法的时候,Linq才真正的开始迭代数据并进行计算。
主要的消耗方法如下所示
4.2.1 转换为集合 (Conversion Operators)
这些方法会强制的遍历整个序列,并将结果存入到新集合中
- ToList()
- ToArray()
- ToDictionart()
- ToHashSet()
4.2.2 聚合计算(Aggregation Operators)
- Count()
- Sum()
- Min()/Max()
- Aggregate()
4.2.3 获取单个元素(Element Operators)
- First()/FirstOrDefault()
- Last()/LastOrDefault()
- Single()/SingleOrDefault()
- ElementAt(index)
- Take()
4.2.4 逻辑判断(Quantifiers)
- Any()
- All()
- Contains()
4.3 需要避免的情况
刚接触Linq的开发者时常会重复调用消耗方法,导致重复遍历数据源,造成不必要的性能浪费
C#
var query = users.Where(u => u.Age >= 18);
//不应到出现的操作
var count = query.Count();
var list = query.ToList();
//推荐做法
var list = query.ToList();
//直接获取,不再遍历
var count = list.Count;
5.PLinq 并行计算
当进行一些非常耗费时间的IO操作时,避免出现行锁是非常重要的事情
C#
var r = new Random(1453);
var arr = Enumerable.Range(0,5).Select(x => r.Next(300));
var res = arr.Select(
x => {
// 一些耗费时间的IO操作
Thread.Sleep(1000);
return x * x;
}
);
res.Dump();
如果你尝试执行这些代码,你几乎都必须等待5000ms以上,这几乎是难以让人接受的,使用AsParallel可以避免这些问题
C#
SyntaxEditor Code Snippet
var r = new Random(1453);
var arr = Enumerable.Range(0,5).Select(x => r.Next(300));
var t1 = DateTime.Now;
var res = arr.AsParallel()
.Select(
x => {
Thread.Sleep(1000);
return x * x;
}
);
res.Dump();
var t2 = DateTime.Now;
//执行耗时00:00:01.0087661
(t2 - t1).Dump();
var t3 = DateTime.Now;
var res2 = arr.Select(
x =>
{
Thread.Sleep(1000);
return x * x;
});
res2.Dump();
var t4 = DateTime.Now;
//执行耗时00:00:05.0573040
(t4 - t3).Dump();
}
可以看到,这带来的性能提升是显著的
6.在什么时候使用Linq?
几乎任何时候,你需要对一个集合(无论是IEnumerable对象,亦或者是配合ORM操作数据库还是其他),当你需要使用for或foreach循环来对数据进行操作的时候,都可以使用Linq,使用Linq总是优雅且高效的。