撰写了文章 发布于 2020-04-14 21:03:48
从C#开始的编程入门——花式操作集合
对于各种集合类,我们会面临各种常见操作。比如统计某个值的出现次数、最大值、最小值、按条件查找等等。这些操作实际上和操作数据库的SQL语言提供的很多功能一样。.NET实际上为.NET上的编程语言也提供了一个类似的内置库用于操作集合(集合是一堆数据的集合,它们有各自的结构,相互之间存在关系,这点来看和数据库有相似之处),它叫做LINQ(读作link,Language Integrated Query,语言集成查询)。实际上LINQ基本上已经不(完全)算是C#语法基础的内容了,主要还是基础类库中提供的一些基础设施,LINQ在处理数据时会非常实用,这篇文章我们主要是了解一下一些常见的用法。
LINQ位于System.Linq命名空间下,其中定义了大量的扩展方法,这些扩展都是针对集合类实现的各种接口定义的。因此我们使用起来也很方便,using之后在各种集合类上直接点出来就可以了。该命名空间下的Enumerable静态类主要就是定义的在IEnumerable上扩展的方法,因此基本上标准类库中的集合类都支持LINQ操作。比如说我们求一组数的平均值:
var numbers = new List<int>{2020, 12, 20, 50};
var avg = numbers.Average();
选择
操作集合时,会遇到选择部分元素的情况,比如选择第一个、最后一个、前五个等等。
First/Last
顾名思义,返回集合的第一个或是最后一个元素,返回类型为集合元素的类型。
var numbers = new List<int>{2020, 12, 20, 50};
var first = numbers.First();
var last = numbers.Last();
Take
Take会返回一个包含集合前n个元素的IEnumerable对象。
var numbers = new List<T>{1,2,3,4,5};
numbers.Take(2); // 1, 2
Select
Select和字面意思的选择有一些不一样,这里选择的是每个集合元素的某个属性,然后将从每个元素中提取出来的数据组成一个新的集合,比如从一个Person类中选择所有人的名字组成一个新的集合:
class Person
{
public string Name{set;get;}
public int Age{set;get;}
}
var aqua = new Person{Name=“Minato Aqua”};
var mio = new Person{Name=“Okami Mio”};
var matsuri = new Person{Name=“Natsuiro Matsuri”};
var vtubers = new List<Person>{aqua, mio, matsuri};
vtubers.Select(v => v.Name); //“Minato Aqua”,”Okami Mio”,”Natsuiro Matsuri”
这里用到了上一篇文章学习的知识。Select方法接受的参数是一个委托,具体类型为Func<T,TResult>,T为**作的集合的元素类型,具体到上面的例子来说就是Person,要求返回一个结果,这个结果就是我们希望从一个Person对象中提取到的某种数据,具体到这里来说就是我们取得每一个Person的名字最后组成一个string类型的可枚举对象,这里直接使用一个简单的lambda即可,其参数类型会根据方法的参数类型进行推断。
过滤
有时候我们希望从一组对象中筛选出所有满足某个条件的对象。
Where
Where和SQL中的where基本一致。Where接受一个Func<T,bool>类型的委托,参数为元素类型,返回一个布尔值用于告诉LINQ某个元素是否满足条件。比如筛选出所有偶数:
var numbers = new List<int>{1, 2, 3, 4, 5};
numbers.Where(n => n % 2 == 0);
Except
和字面意思一样,它会返回排除一组元素后的结果。比如我们把前面的偶数排除掉,剩下的就是奇数了。
numbers.Except(numbers.Where(n => n % 2 == 0));
查找
Find/FindLast/FindAll
Find查找并返回某个元素。它接受一个Predicate<T>类型的委托,实际上是一个接受元素类型,返回布尔值的委托类型。也就是根据条件判断某个对象是不是要找的对象。如果满足条件的对象存在,Find会返回第一次出现的对象,而FindLast返回最后一次出现的对象。FindAll会找到所有满足条件的对象并构造一个List。
var aqua = new Person{Name=“Minato Aqua”, Age=5};
var mio = new Person{Name=“Ookami Mio”, Age=17};
var matsuri = new Person{Name=“Natsuiro Matsuri”, Age=38};
var vtubers = new List<Person>{aqua, mio, matsuri};
List<Person> under21 = vtubers.FindAll(v => v.Age <= 21);
vtubers.Find(v => v.Age < 21); // Aqua
vtubers.FindLasast(v => v.Age > 21); // Matsuri
FindIndex/FindLastIndex
使用方法和Find系方法类似,但是带Index字样的方法是在ICollection上扩展的,因为至少需要ICollection才提供了索引器。这些方法在查找到对应的元素后会返回对应的索引。
var numbers = new List<int>{1,2,2,1};;
numbers.FindIndex(0, n => n == 1); // 0
numbers.FindLastIndex(n => n == 1); // 3
值得注意的是,FindIndex必须指定起始位置,但是FindLastIndex不用。
聚合
数据库相关技术中所谓的聚合函数就是指的将多行数据放在一起进行一些操作最终得到一个关于这组数据的值。比如说平均值、最值这些都算是聚合函数。LINQ也提供了类似的操作。
Sum
即求和。对于数值类型的集合,直接加上即可。但对于很多非数值类型的集合来说,必须提供更详细的参数来产生可以求和的数据,因此Sum的一些重载版本实际上接受的是一个委托,用来产生对象中可以进行求和的数据。
var numbers = new List<int>{1, 2, 3, 4, 5};
numbers.Sum();
vtubers.Sum(v => v.Age); // 参见前面Person的定义,这里对年龄求和。
Max/Min
求最大值和最小值,和Sum类似,数值类型直接按照一般定义求,非数值类型需要进一步考虑。如果使用不需要参数的版本,那么要么你的集合元素是数值类型,要么就需要实现IComparable接口以便LINQ比较各个元素的大小。对于接受委托的版本,和Sum是一样的道理。
Count
顾名思义,计数。主要提供了两个版本,无参数的版本就是单纯求出集合有多少个元素(注意不要和很多集合类型的Count属性搞混,LINQ中的Count是一个方法,它始终都有一对括号,不过实际上如果没有参数的版本和Count属性是等效的)。接受Func<T,bool>类型委托的版本实际上就是只记满足条件的数的个数。比如要计算有多少个大于10的数:
numbers.Count(n => n > 10);
排序
OrderBy方法用于对集合元素进行升序排序。参数为一个委托,委托的返回结果是一个关键字,也就是按照什么排序。OrderByDescending就是按降序排序。比如要把前面的Person按年龄排序。
vtubers.OrderBy(v => v.Age);
链式调用
LINQ提供的方法通常会返回另一个可枚举的对象,因此我们可以将各种不同的操作进行最后,使用一条语句就得到我们想要的结果,简洁又易读(当然你也有可能需要中间产物)。比如;挑选18岁以上的人,然后按年龄倒序排序,选择前十个人,最后产生名单(为了好看我换了行):
vtubers.OrderByDescending(v => v.Age)
.Where(v => v.Age > 18)
.Take(10)
.Select(v => v.Name);
延迟执行
由于LINQ操作主要针对包含多个元素的集合,因此你可能会想到它会不会造成性能问题。实际上当我们写下这些LINQ方法时,相关的查询并没有立即执行,而是会延迟到你第一次尝试迭代(比如说用在foreach中)LINQ查询返回的IEnumerable对象时执行。这种概念常被称为延迟执行(deferred execution)。你可以认为LINQ返回的结果实际上只是记录了相关操作所需信息。
那么如果我们需要立即构造一个单独的结果又该怎么办呢?比如我们查询到所有数学不及格的学生,然后要作为一个List传递给另一个方法呢?
立即执行
LINQ的很多方法返回的都是IEnumerable,我们可以使用To开头的方法立即获得结果并保存在其它集合类型实例中。例如获取所有偶数然后保存在一个List中:
var even = numbers.Where(n => n % 2 == 0).ToList();
实际上LINQ返回的IEnumerable并不是一个新的集合,而是代表着原集合的一部分,一旦使用ToList一类的方法立即获得结果,这些结果就会被复制并保存在一个新的集合中,这个集合不再和原来的集合有联系。
除了ToList,还有ToArray等等。
像SQL一样写LINQ
C#在语法层面提供了LINQ的特殊语法,可以让你像写SQL语句一样。不过我个人决定这种语法写起来实在显得太突兀,而且格式很难调整。很多人都更喜欢直接调用扩展方法,因此这里只做简单介绍。详情可以参考这里。
像SQL中的select、from、where、orderby等等关键字都悉数在线。
var result = from n in numbers where n % 2 == 0 orderby n select n;
// numbers.OrderBy(n => n).Where(n => n % 2 == 0);
LINQ是一个内容丰富的库,这里只列举了其中一些常用的操作。更多的可以参看文档内容。除了操作.NET标准库中的集合,LINQ还可以和各种数据库系统搭配,使用LINQ的模式来进行数据库的查询和操作,也可以将XML等结构化文本进行抽象通过LINQ来操作,是非常实用的基础功能。
目录
世俗骑士 1年前
有人使用这个做了骑砍2的部队排序mod,我看源代码的时候被秀到了。
黛黛冬优子 [作者] 1年前
黑白色的枫 1年前
世俗骑士 1年前
黑白色的枫 1年前
发布
DragoonKiller 1年前
黛黛冬优子 [作者] 1年前
发布
Ellsworth 1年前
发布