当前位置: 首页 > news >正文

[C#学习笔记]LINQ

视频地址:LINQ入门示例及新手常犯的错误_哔哩哔哩_bilibili

强烈推荐学习C#和WPF的朋友关注此UP,知识点巨多,讲解透彻!

一、基本概念

语言集成查询(Language-Intergrated Query)

常见用途

  • .Net原生集合(List,Array,Dictionary,etc.)
  • SQL数据库(尤其搭配ORM)
  • XML文档
  • JSON文档(Newtonsoft.Json)

常见功能

  • 排序、筛选、选择
  • 分组、聚合、合并
  • 最大值,最小值,求和,求平均,求数量
  • ......

两种形式

  • 查询表达式 query expression
  • 链式表达式 chained expression

例如,现在有个List<int>,内容为0-9,无序排列,需要把其中大于等于4的元素取出并排序

普通写法

var lst = new List<int> {1,3,5,7,9,2,4,6,8,0};var res = new List<int>();
foreach (var n in lst)
{if (n %2 == 0 && n >= 4) res.Add(n);
}res.Sort();
res.Dump();

查询表达式:

var lst = new List<int> {1,3,5,7,9,2,4,6,8,0};var res = from n in lstwhere n % 2== 0 && n >= 4orderby nselect n;res.Dump();

链式表达式:

var lst = new List<int> {1,3,5,7,9,2,4,6,8,0};var res = lst
.Where(n=> n%2 == 0 && n>= 4)
.OrderBy(n=> n);res.Dump();

二、例程

2.1 取两个数组的交集

普通写法

var arr1 = new int[]{1,2,3,4,5,6};
var arr2 = new int[]{4,5,6,7,8,9};var res = new List<int>();
foreach (var n in arr1)
{if (arr2.Contains(n)) res.Add(n);
}res.Dump();

查询表达式

var arr1 = new int[]{1,2,3,4,5,6};
var arr2 = new int[]{4,5,6,7,8,9};var res = from n in arr1 where arr2.Contains(n)select n;res.Dump();

链式表达式

var arr1 = new int[]{1,2,3,4,5,6};
var arr2 = new int[]{4,5,6,7,8,9};var res = arr1.Intersect(arr2);res.Dump();

2.2 统计数组中数字的频率

普通写法

var rnd = new Random(1334);
var arr = Enumerable.Range(0, 200).Select(_ => rnd.Next(20));var dic = new Dictionary<int, int>();
foreach (var n in arr)
{if (dic.TryGetValue(n, out int value)){dic[n] = value +1;}else {dic[n] = 1;}
}dic.Dump();

查询表达式

var rnd = new Random(1334);
var arr = Enumerable.Range(0, 200).Select(_ => rnd.Next(20));var dic = from n in arrgroup n by n into gselect new { g.Key, Count = g.Count() };dic.Dump();

其中

new { Key = g.Key, Count = g.Count() }

为匿名类写法

链式表达式

var rnd = new Random(1334);
var arr = Enumerable.Range(0, 200).Select(_ => rnd.Next(20));var dic = arr.GroupBy(x => x).Select(g => new {Key = g.Key, Count = g.Count()});dic.Dump();

三、重要概念

3.1 延迟执行

LINQ表达式在定义时不会真正的执行,只有在真正消耗时才会执行。

代码一:

var lst = new List<int> { 1, 2, 3, 4, 5 };
var query = lst.Select(x=>x*x);lst.Add(6);
query.Dump();

运行结果为

代码二:

var lst = new List<int>{1,2,3,4,5};Stopwatch watch = new Stopwatch();
watch.Start();
var query = lst.Select(x =>
{Thread.Sleep(500);return x*x;
});watch.Dump();
query.ToList();
watch.Dump();

运行结果

 通过这两个代码可以看出,如果没有对查询表达式进行消耗的操作,表达式并不会真正执行

3.2 消耗

  • 遍历 foreach
  • ToList()、ToArray()、ToDictionary()、
  • Count()、Min()、Max()
  • Take()、First()、Last()

3.3 LINQ并不仅仅是可枚举类型的扩展方法

  • IEnumerable
  • IOrderedEnumerable
  • IQueryable
  • ParallelQuery

四、扩展例程

4.1 展平

将多维数组转为一维形式

查询表达式

var mat = new int[][]{new [] {1,2,3,4},new [] {5,6,7},new [] {8,9,10,11,12}
};var res = from row in matfrom data in rowselect data;res.Dump();	

链式表达式

var mat = new int[][]{new [] {1,2,3,4},new [] {5,6,7},new [] {8,9,10,11,12}
};var res = mat.SelectMany(x=>x);res.Dump();	

运行结果

4.2 笛卡尔积

普通写法

for (int i = 0; i < 5; i++)
{for	(int j = 0; j < 4; j++){for (int k = 0; k < 3; k++){$"{i},{j},{k}".Dump();}}
}

运行结果


查询表达式

var prods = from i in Enumerable.Range(0, 5)from j in Enumerable.Range(0, 4)from k in Enumerable.Range(0, 3)select $"{i},{j},{k}";prods.Dump();

链式表达式

var prods = Enumerable.Range(0, 5).SelectMany(r => Enumerable.Range(0, 4), (l,r)=>(l,r)).SelectMany(r => Enumerable.Range(0, 3), (l,r)=>(l.l, l.r, r)).Select(x=>x.ToString());prods.Dump();

4.3 字母频率

查询表达式

var words = new string[]{"tom", "jerry", "spike", "tyke", "butch", "quacker"};var query = from w in wordsfrom c in wgroup c by c into gselect new {g, Count=g.Count()} into aorderby a.Count descendingselect a;query.Dump();

结果

链式表达式

var words = new string[]{"tom", "jerry", "spike", "tyke", "butch", "quacker"};var query = words.SelectMany(x=>x).GroupBy(x=>x).Select(x => new { x, Count = x.Count() }).OrderByDescending(x => x.Count);query.Dump();

4.4 批量下载文件

普通写法

var urls = new string[]
{"http://www.example.com/pic1.png","http://www.example.com/pic2.png","http://www.example.com/pic3.png"
};$"Tasks start at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();
foreach (var url in urls)
{await DownloadAsync(url, url.Split('/').Last());
}
$"Tasks end at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();async Task DownloadAsync(string url, string filename)
{await Task.Delay(1000);$"{filename} downloaded at {DateTime.Now.ToString("HH:mm:ss ffff")}.".Dump();
}

输出结果,可以看到3个任务花了3秒

Tasks start at 16:48:19 7820
pic1.png downloaded at 16:48:20 7829.
pic2.png downloaded at 16:48:21 7841.
pic3.png downloaded at 16:48:22 7844.
Tasks end at 16:48:22 7845

改进写法

var urls = new string[]
{"http://www.example.com/pic1.png","http://www.example.com/pic2.png","http://www.example.com/pic3.png"
};var tasks = new List<Task>();
foreach (var url in urls)
{tasks.Add(DownloadAsync(url, url.Split("/").Last()));
}$"Tasks start at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();
await Task.WhenAll(tasks);
$"Tasks end at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();async Task DownloadAsync(string url, string filename)
{await Task.Delay(1000);$"{filename} downloaded at {DateTime.Now.ToString("HH:mm:ss ffff")}.".Dump();
}

运行结果,可以看到3个任务花了1秒

Tasks start at 16:47:18 1255
pic1.png downloaded at 16:47:19 1259.
pic3.png downloaded at 16:47:19 1259.
pic2.png downloaded at 16:47:19 1259.
Tasks end at 16:47:19 1265

 查询表达式

var urls = new string[]
{"http://www.example.com/pic1.png","http://www.example.com/pic2.png","http://www.example.com/pic3.png"
};var tasks = from url in urlsselect DownloadAsync(url, url.Split("/").Last());$"Tasks start at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();
await Task.WhenAll(tasks);
$"Tasks end at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();async Task DownloadAsync(string url, string filename)
{await Task.Delay(1000);$"{filename} downloaded at {DateTime.Now.ToString("HH:mm:ss ffff")}.".Dump();
}

链式表达式

var urls = new string[]
{"http://www.example.com/pic1.png","http://www.example.com/pic2.png","http://www.example.com/pic3.png"
};var tasks = urls .Select(url => DownloadAsync(url, url.Split("/").Last()));$"Tasks start at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();
await Task.WhenAll(tasks);
$"Tasks end at {DateTime.Now.ToString("HH:mm:ss ffff")}".Dump();async Task DownloadAsync(string url, string filename)
{await Task.Delay(1000);$"{filename} downloaded at {DateTime.Now.ToString("HH:mm:ss ffff")}.".Dump();
}

五、避坑集合

5.1 尽量使用自带方法

5.1.1 First()、Last()

比如说一个集合处理完后,需要得到其第一个元素,使用以下写法

var arr = new List<int>{1,3,5,7,9,2,4,6,8,0};arr.Select(x=>x).ToList()[0].Dump();

虽然也得到了结果,但是多占用了内存,而是应该尽量使用IEnumerable<T>自带的方法

var arr = new List<int>{1,3,5,7,9,2,4,6,8,0};arr.Select(x=>x).First().Dump();

5.1.2 Average()

不要使用Sum+Count方法求平均数:

var arr = new List<int>{1,3,5,7,9,2,4,6,8,0};(arr.Sum()/arr.Count).Dump();

使用自带方法Average

var arr = new List<int>{1,3,5,7,9,2,4,6,8,0};arr.Average().Dump();

5.1.3 Count()、First()、Min()、Max()等方法可以传参

比如查找第一个偶数

使用Where+First

var arr = new List<int>{1,3,5,7,9,2,4,6,8,0};arr.Where(x=>x%2==0).First().Dump();

可以把条件直接翻入First

var arr = new List<int> { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };var res = from x in arrselect x;

5.1.4 Max()和MaxBy()的区别

比如说,查找年龄最大的对象

var people = new List<Person>
{new ("Tom", 18),new ("Jerry", 19),new ("Nason", 20),
};var age = people.Max(x=> x.Age);
var p = people.First(x=>x.Age == age);
p.Dump();record Person(string Name, int Age);

其实使用MaxBy就可简洁实现

var people = new List<Person>
{new ("Tom", 18),new ("Jerry", 19),new ("Nason", 20),
};var p = people.MaxBy(x=>x.Age);
p.Dump();record Person(string Name, int Age);

5.1.5 Default的使用

First和FirstOrDefault,Last和LastOrDefault,等等

比如说以下代码

var people = new List<Person>
{new ("Tom", 18),new ("Jerry", 19),new ("Nason", 20),
};if (people.Any(x=>x.Age >= 21))people.First(x=>x.Age >= 21).Dump();record Person(string Name, int Age);

使用FirstOrDefault后

var people = new List<Person>
{new ("Tom", 18),new ("Jerry", 19),new ("Nason", 20),
};people.FirstOrDefault(x=>x.Age >= 21).Dump();record Person(string Name, int Age);

5.2 注意开销

5.2.1 滥用ToList(),arr.Where().OrderBy().ToList()[0]

非必要不使用ToList,因为会多占用空间

5.2.2 滥用Count(),Count()>0

在判断集合中是否存在符合某个条件的元素时,应该使用Any(),

因为Count()是需要遍历所有元素,Any()是遇到第一个符合条件的就返回(最极端时才会遍历所有元素)

5.2.3 滥用OrderBy(),不适用Sort

OrderBy().ToList()需要重新开辟内存空间,Sort()是在原有集合上排序,不会多增加空间

而且OrderBy(x=>x)使用了lamda表达式,执行时间更长

5.2.4 不知道First()与Single()的区别

First是返回第一个符合条件的元素(可能有多个)

Single是有且只有一个

6. LINQ个人总结

6.1 命名

6.1.1 from

from后面是变量及变量的命名,作用域为当前LINQ语句。

var arr = new List<int> { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
var res = from x in arrselect x;
var res = from x in Enumerable.Range(0, 5)from y in Enumerable.Range(10, 15)from z in Enumerable.Range(20, 25)select new {First = x, Second = y, Third = z} ;res.Dump();

6.1.2 into

from前面是变量,后面是变量的命名,作用域为当前LINQ语句。

var query =from x in Enumerable.Range(0, 5)select new { x, Square = x * x} into yselect y;query.Dump();

6.1.3 let

let后面等号前为变量名,等号后为变量值,作用域为当前LINQ语句。

var query =from x in Enumerable.Range(0, 5)select new { x, Square = x * x} into ylet result = y.Squareselect result;query.Dump();

6.2 结尾

LINQ语句结尾必须要是select

6.3 Join

Join 个操作 - C# | Microsoft Learn

var students = new List<Student>()
{new (){ FirstName = "Bruce", LastName = "Cambell", ID = 10, Year = GradeLevel.FirstYear, DepartmentID = 223},new (){ FirstName = "Cindy", LastName = "Haneline", ID = 11, Year = GradeLevel.SecondYear, DepartmentID = 300},new (){ FirstName = "Andrea", LastName = "Deville", ID = 12, Year = GradeLevel.ThirdYear, DepartmentID = 400},
};var teachers = new List<Teacher>()
{new (){ First = "Anita", Last = "Ryan", ID = 32, City = "NewYork" },new (){ First = "George", Last = "Bunkelman", ID = 44, City = "Washington"},new (){ First = "Andrew", Last = "Carter", ID = 50, City = "LosAngeles"}
};var departments = new List<Department>()
{new (){ Name = "Secretariat", ID = 500, TeacherID = 32},new (){ Name = "General Office", ID = 300, TeacherID = 44},new (){ Name = "Law Committee", ID = 400, TeacherID = 50},
};public enum GradeLevel
{FirstYear = 1,SecondYear,ThirdYear,FourthYear
};public class Student
{public string FirstName { get; init; }public string LastName { get; init; }public int ID { get; init; }public GradeLevel Year { get; init; }public int DepartmentID { get; init; }
}public class Teacher
{public string First { get; init; }public string Last { get; init; }public int ID { get; init; }public string City { get; init; }
}
public class Department
{public string Name { get; init; }	public int ID { get; init; }public int TeacherID { get; init; }
}

6.3.1 Join ... in ... on ... equals ...

基于特定值联接两个序列


var query = from student in studentsjoin department in departments on student.DepartmentID equals department.IDselect new { Name = $"{student.FirstName} {student.LastName}", DepartmentName = department.Name };query.Dump();

6.3.2 join … in … on … equals … into …

基于特定值联接两个序列,并对每个元素的结果匹配项进行分组


IEnumerable<IEnumerable<Student>> studentGroups = from department in departmentsjoin student in students on department.ID equals student.DepartmentID into studentGroupselect studentGroup;
studentGroups.Dump();

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 企业微信运营工具:赋能企业数字化转型的利器
  • Playwright 和 Selenium的对比
  • 7.认识进程
  • 积分第二中值定理的证明
  • 结构开发笔记(七):solidworks软件(六):装配摄像头、摄像头座以及螺丝,完成摄像头结构示意图
  • 智慧赋能,铸就国防工业新辉煌-程易科技助力某军工企业数字化转型纪实
  • 【区块链通用服务平台及组件】微言科技数据智能中台
  • 一个成熟的软件测试工程师应该具备那些“技能”
  • 探索Python的隐秘角落:Keylogger库的神秘面纱
  • MYSQL的结构及常用语句
  • 使用jackson将json转换为对象时,自定义转换字段内容
  • 简单计算机网络概念
  • 与 D3.js 的对比:ECharts 在前端可视化中的优势与劣势
  • 数据库类型有哪些?
  • C++ Windwos 文件操作
  • 分享一款快速APP功能测试工具
  • CAP理论的例子讲解
  • crontab执行失败的多种原因
  • Mithril.js 入门介绍
  • mongo索引构建
  • Netty源码解析1-Buffer
  • SpriteKit 技巧之添加背景图片
  • v-if和v-for连用出现的问题
  • Vue.js-Day01
  • zookeeper系列(七)实战分布式命名服务
  • 阿里云ubuntu14.04 Nginx反向代理Nodejs
  • 大快搜索数据爬虫技术实例安装教学篇
  • 聚类分析——Kmeans
  • 前端相关框架总和
  • 通过几道题目学习二叉搜索树
  • 自动记录MySQL慢查询快照脚本
  • Oracle Portal 11g Diagnostics using Remote Diagnostic Agent (RDA) [ID 1059805.
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • #多叉树深度遍历_结合深度学习的视频编码方法--帧内预测
  • #我与Java虚拟机的故事#连载07:我放弃了对JVM的进一步学习
  • (1)Hilt的基本概念和使用
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (4) PIVOT 和 UPIVOT 的使用
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)springboot宠物医疗服务网站 毕业设计688413
  • (亲测有效)解决windows11无法使用1500000波特率的问题
  • .net8.0与halcon编程环境构建
  • .net安装_还在用第三方安装.NET?Win10自带.NET3.5安装
  • ?.的用法
  • @column注解_MyBatis注解开发 -MyBatis(15)
  • @JSONField或@JsonProperty注解使用
  • @RequestParam @RequestBody @PathVariable 等参数绑定注解详解
  • @SentinelResource详解
  • [BT]小迪安全2023学习笔记(第29天:Web攻防-SQL注入)
  • [BUUCTF 2018]Online Tool(特详解)
  • [C#]OpenCvSharp结合yolov8-face实现L2CS-Net眼睛注视方向估计或者人脸朝向估计
  • [C++] Boost智能指针——boost::scoped_ptr(使用及原理分析)
  • [CareerCup] 2.1 Remove Duplicates from Unsorted List 移除无序链表中的重复项
  • [Codeforces] number theory (R1600) Part.11
  • [codevs 2822] 爱在心中 【tarjan 算法】