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

相关推荐
devlei1 天前
从源码泄露看AI Agent未来:深度对比Claude Code原生实现与OpenClaw开源方案
android·前端·后端
努力的小郑1 天前
Canal 不难,难的是用好:从接入到治理
后端·mysql·性能优化
Victor3561 天前
MongoDB(87)如何使用GridFS?
后端
Victor3561 天前
MongoDB(88)如何进行数据迁移?
后端
小红的布丁1 天前
单线程 Redis 的高性能之道
redis·后端
GetcharZp1 天前
Go 语言只能写后端?这款 2D 游戏引擎刷新你的认知!
后端
宁瑶琴1 天前
COBOL语言的云计算
开发语言·后端·golang
普通网友1 天前
阿里云国际版服务器,真的是学生党的性价比之选吗?
后端·python·阿里云·flask·云计算
IT_陈寒1 天前
Vue的这个响应式问题,坑了我整整两小时
前端·人工智能·后端
Soofjan1 天前
Go 内存回收-GC 源码1-触发与阶段
后端