C#中的Linq(Language Integrated Query)

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总是优雅且高效的。

相关推荐
小蒜学长2 小时前
python基于Python的医疗机构药品及耗材信息管理系统(代码+数据库+LW)
数据库·spring boot·后端·python
苏近之2 小时前
Rust 基于 Tokio 实现任务管理器
后端·架构·rust
zzlyx992 小时前
ASP.NET Core 依赖注入的三种服务生命周期的不同使用
后端·asp.net
踏浪无痕2 小时前
像挑选书籍一样挑选技术:略读、精读,还是直接跳过?
后端·程序员·架构
源代码•宸2 小时前
goframe框架签到系统项目开发(用户认证、基于 JWT 实现认证、携带access token获取用户信息)
服务器·开发语言·网络·分布式·后端·golang·jwt
期待のcode2 小时前
static关键字
java·后端
SimonKing2 小时前
Java汉字转拼音的四种方案,99%的开发场景都够用了!
java·后端·程序员
kobe_OKOK_3 小时前
windows 部署 django 的 方案
后端·python·django