.NET入门: 使用linq,替代for循环遍历
Part 0 为什么要使用linq 🍌
我们来看一个简单的需求:
var numbers = new List<List<int>>{
new List<int>{1,2,3,4},
new List<int>{5,6,7,8,9},
new List<int>{10,11,12}
};
这是一组数字,现在我需要你将里面的所有偶数挑出来,这当然很好实现。
如果你只会使用for循环:
for (int i = 0; i < numbers.Count; i++) {
for (int j = 0; j < numbers[i].Count; j++) {
if (numbers[i][j] % 2 == 0) {
Console.WriteLine(numbers[i][j]);
}
}
}
这很简单,任何学过编程的同学都会。
如果你会foreach,那可以更简单:
foreach (var number in numbers) {
foreach (var item in number) {
if (item%2==0) Console.WriteLine(item);
}
}
接下来我将告诉你更简便的方法,那就是用linq。
foreach (var number in numbers.SelectMany(x => x).Where(x => x % 2 == 0)) {
Console.WriteLine(number);
}
这里引用一段🤖GPT对LInq的解释:
C# 中的 LINQ(Language Integrated Query)是一种编程技术,
它允许开发者使用类似 SQL 的查询语法来操作集合、数组、数据库等数据源。
LINQ 提供了一种统一的方式来查询和转换数据,使得代码更加简洁和易于理解。
使用linq可以直接对数据进行查询、处理。而不需要再使用for循环遍历的方式,不仅可以精简代码,
也可减少人为实现查询方法,因为疏忽而导致的错误。
Part 1 linq的简单使用 🖥
linq主要的使用方式分为两种,一种是语句式,一种是函数调用式。
上述例子🌰当中使用的就是函数调用式。
这里让🤖gpt随便生成了一些数据和数据定义
var students = new List<Student>
{
new Student(1, "Alice", 20, "Sophomore", Gender.Female),
new Student(2, "Bob", 21, "Junior", Gender.Male),
new Student(3, "Charlie", 22, "Senior", Gender.Male),
};
public enum Gender
{
Male,
Female
}
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Grade { get; set; }
public Gender Gender { get; set; } // 使用Gender枚举类型
public Student(int id, string name, int age, string grade, Gender gender)
{
Id = id;
Name = name;
Age = age;
Grade = grade;
Gender = gender;
}
}
生成的数据如下:
ID | Name | Age | Grade | Gender |
---|---|---|---|---|
1 | Alice | 20 | Sophomore | Female |
2 | Bob | 21 | Junior | Male |
3 | Charlie | 22 | Senior | Male |
现在我们需要拿出年龄大于等于21岁的所有学生信息,
在学会使用linq之后,我们不再需要遍历整个students,然后判断年龄是否符合条件。
var studentInfo = from stu in students
where stu.Age >= 21
select stu;
//或者
var studentInfo = students.Where(stu=>stu.Age>=21);
可能你已经发现 🔍了差异。为什么使用类似sql语句的语句式linq比函数式多了一行select?
🚨 这是因为使用语句式linq必须以select或者group结尾。 🚨
函数式也可以写作:
var studentInfo = students.Where(stu=>stu.Age>=21).Select(stu=>stu);
当然,这并没有什么意义,结果完全一样。
对于不了解lambda表达式的同学,简单解释一下放进函数式括号里面的❓是什么东东。
你大致可以理解为箭头前面的是指代集合列表中的每一个元素,就像foreach一样,箭头后面是返回值。
语句式和sql语句很像,这里需要注意的是函数式的使用。
var studentInfo = students.Where(stu=>stu.Age>=21);
var studentInfo = students.Select(stu=>stu.Age>=21);
这两行执行结果并不一样,where是从集合列表中选出符合条件的元素,返回的是元素的列表。
select选什么就会返回什么。例如在此处的返回结果其实是{false,true,true}。
对应的就是三个学生对象的年龄是否超过21岁。
我们再举个栗子🌰,你就能明白两者的差别:
var studentInfo = students.Select(stu=>stu.Age+1)
你知道返回结果是什么吗?age+1显然不是一个条件。结果是 {21,22,23}。
没错,就是每个元素的age+1。
如果你使用where语句,是不能使用这个条件的,条件的返回值必须得是一个bool值。
学会了linq应该如何使用,我们就可以开始使用一些其他的功能。例如分组、排序。
var studentInfo = from stu in students
group stu by stu.Gender into s
select s.Key + " :" + s.Count();
//into是给查询的结果取一个别名,用于之后的查询
//或者
var studentInfo = students.GroupBy(stu=>stu.Gender).Select(s=>s.Key+":"+s.Count());
上面的例子🌰中我们就得到了男女的人数分别是多少。
需要说明的是,分组之后返回的是一个IGrouping<TKey,TElement>的列表。其中Key就是用于分组的那一项。
同样我们可以用linq排序,而不需要实现一个专门的排序函数。
var studentInfo = students.OrderBy(stu=>stu.Age);
//默认是从小到大排序,如果你需要反序,使用OrderByDescending函数
var studentInfo = from stu in students
orderby stu.Age
select stu;
//如果你需要反序,在select前stu.Age后,加上Descending关键字
于是我们将学生按照年龄进行了排序,单独实现排序当然也可以实现比较器,然后掉用sort函数。
linq不止可以排序,还可以对排序的结果再进行查询。
关于linq的详细使用,可以参考
官方文档
需要注意的是,linq虽然很像sql,但并不是sql语句。例如在文章一开头提到的🌰
传送门
(关于SelectMany的使用,这里不展开说明。你可以大致理解为,对于多维数组,这个函数可以直接取到最低层的元素本身。而不是每一维度的集合。)
例子中,如果使用语句式应该
var number =from num in numbers
from n in num
where n%2==0
select n;
而不是使用如同sql中的子查询,即查询嵌套。
至此你就已经可以愉快在.NET中使用linq了,每次查询处理集合列表的数据,不再需要使用for循环遍历。
🐞<font color=Red>使用应注意:</font>🐞
在使用linq前,你需要知道的是linq只是一种声明,类似于给集合列表添加某种规则,如果你不把结果foreach或者赋值(任何涉及到需要获取集合列表内容的操作都算,比如调用列表自带的count、get函数),linq本身不会被执行。 所以如果你只是对列表集合使用linq,而不使用返回的查询结果,是没有任何意义的。
例如:
students.Where(s=>s.Age>21);
运行完之后并不会对students进行修改,而是返回一个符合查询结果的List。
此外,即使你使用一个变量接住返回值,如果你不使用这个变量进行其他操作,也依旧不会执行相应的linq语句。
这里给出一个例子🐞
var stus=students.Where(s => s.Age >= 21);
students.Add(new Student(0,"张三",22,"初二",Gender.Male));
foreach (var item in stus)
{
Console.WriteLine(item.Name);
}
我们在执行完linq之后,再向students添加了一个新的同学张三。似乎不会影响stus的值。
但是,如果你运行了上述代码,就会发现🔍输出结果是
Bob
Charlie
张三
原因就在于linq是在forech拿取stus元素时,换句话说就是在获取stus的内容时才被调用了。stus赋值声明操作并没有真正调用linq。
你可以这么理解linq,你有一个天天宅在宿舍🎮打游戏的同学sunsun🌞。
sunsun同学有个好习惯,那就是老师布置的作业,什么时候要收,才开始写。
如果你不知道sunsun同学的习惯。先布置第一次作业,然后不收。
之后又布置了第二次作业,此时再收第一次作业。
这个时候急急急的😰sunsun同学就会开始补作业,把两次作业一起交了。
上述代码给出的就是这么一种情况,我们先使用linq,查询了Age>21的所有同学。
因为 stus并没有被使用,里面的值不需要被修改或获取。🌞🌞同学就会选择不执行这个查询。
当我们遍历stus时,要用到stus的值了,😰😰linq才会对students进行查询。由于在这个之前,
我们往students里又插入了一条数据,所以也会被linq查询到。
🐞这是你使用时应该注意的。使用完linq后应该对linq的结果直接使用,否则如果你对原先整个集合列表进行其他操作,可能会影响到你之前linq返回的结果。
Part EX linq的常用函数 🎮
linq其实和sql一样,也有join操作。
linq的函数式中有些比较常用的方法,如first,last。获取列表集合的第一个和最后一个元素。
像项目中常用的应该是FirstOrDefault,LastOrDefault方法,避免查询结果为空的情况。
count(//lambda表达式 //) | 可以获取到符合条件的元素的数量。 |
Average(//lambda表达式 //) | 求对应的平均值。 |
Sum(//lambda表达式 //) | 同上。 |
MaxBy(//lambda表达式 //) | 可以获取对应满足对应key最大值的对象。 |
MinBy(//lambda表达式 //) | 同上。 |
any(//lambda表达式或者空//) | 判断符合条件的结果是否为空,因为是随机拿第一个值返回,所以效率比count遍历要高。 |
使用这些方法可以极大的简化查询过程的实现,更多的常用函数可以查看对应的官方文档。
官方文档
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。