代码改变世界

LINQ本质(再版)第二篇 运算

2008-10-14 19:54  Ivony...  阅读(3438)  评论(8编辑  收藏  举报

上一篇文章的论述中我们可以知道,.NET Framework使用IEnumerable<T>表示一个关系(集合),但反之不然。

不过IEnumerable<T>的本质是一个序列枚举器,所以IEnumerable<T>本身就具备三重特性,集合、关系、序列。

 

.NET Framework定义了一个扩展类Enumerable用于对IEnumerable进行运算扩展,主要可以分为四部分:

一、查询/集合运算

  

1、集合运算

Intersect 交集运算

Union 并集运算

Except 差集运算

 

2、查询运算

Where 筛选

Select和SelectMany 投射

GroupBy 分组

Join和GroupJoin 连接

OrderBy、OrderByDescending、ThenBy和ThenByDescending 排序

 

3、集合谓词逻辑

Any

All

 

二、聚合运算

Aggregate 迭代聚合

Max 最大

Min 最小

Sum 求和

Average 平均值

Count和LongCount 计数

 

三、序列运算

Take和TakeWhile

Skip和SkipWhile

First和FirstOrDefault

Last和LastOrDefault

ElementAt和ElementAtOrDefault

Contact

Reverse

 

四、转换

OfType 按类型筛选

Distinct 去除重复值

 

ToArray

ToDictionary

ToList

ToLookup

对于LINQ而言,我们主要关注的是查询运算,即筛选、投射、分组、连接和排序。

LINQ为C#和VB两种语言都发明了一种叫做LINQ Expression的东西来辅助我们编写查询,避免去冗长的调用那些方法。在这里,我们简单的对C#所支持的LINQ Expression做一个介绍。

C#的LINQ Expression总是以一个from子句开始,from子句的格式与foreach的有些类似:

from element in set

from子句的最大作用在于定义元素与集合的关系用于后文使用,set是集合(IEnumerable<T>),element是集合元素(类型自动推导为T)。

只有from子句的LINQ Expression是不合法的。一个合法的LINQ Expression必须要有select子句或者group by子句(因为只有这两个子句才造成输出),下面对这两个子句作一简单介绍。

IEnumerable<string> result = from item in list select item.ToString();

这条语句的用途为将list中的每一项(item)调用ToString方法,并将结果集合返回,所以这个LINQ Expression的返回类型就是IEnumerable<string>。select子句可以直接创建匿名对象,不过如果创建匿名对象,就必须用var来代替明确的返回类型。另外,LINQ Expression不是一个可以独立成为语句的表达式,所以下面的代码会是一个编译错误:

from item in list select item.ToString(); 

 

group by子句由两个关键字组成,group和by,语法如下:

group item by key

item是需要被包括于分组中的项,而key则是分组依据。group by子句执行的操作是:对于集合中的每一个元素,检索其key的值,key值相同的元素的item组成一个集合,生成一个IGrouping<TKey,TValue>类型的对象。这是一个包含一个key值(IGrouping.Key属性)和一个item集合(IGouping继承于IEnumerable<TValue>)的对象,每一个这样的对象对象表示一个key值的分组。而group by最终的结果为这些分组的集合,即group by操作的最终结果是一个类型为IEnumerable< IGrouping< key的类型, item的类型 > >的对象。

 

下面这个表达式用于数据分组(假设data是一个这样的类型:IEnumerable<Data>,而Data{ string Key, int Value }):

var result = from item in data group item by item.Key

但是这个result的结果是IEnumerable<IGrouping<string,Data>>

 

如果我们希望对分组后的Value进行求和(Sum),就必须对其再进行一次select:

var result2 = from item in result select new { Key = item.Key, Sum = item.Sum( dataItem => dataItem.Value ) };

//小说明,因为IGrouping<string,Data>继承于IEnumerable<Data>,所以这里的Sum方法其实正是Enumerable类中定义的扩展方法IEnumerable<Data>.Sum( Func<Data,int> selector )

 

这样的写法非常麻烦,而且也难看,所以C#提供了into关键字来帮助我们改善这种语法:

var result = from item in data group item by item.Key into groupItem select new { Key = groupItem.Key, Sum = groupItem.Sum( dataItem => dataItem .Value ) };

 

into的语法其实很简单,可以视为把from翻转过来。

set into element

只不过在这里set是一个LINQ表达式。但实际上from子句中的set也可以是一个表达式,LINQ Expression中绝大多数子句的参数都可以是一个表达式,一般而言只要类型匹配就可以使用。

 

 

最后,我们发现其实只有Value属性参与了统计,所以这个表达式还可以简化如下:

var result = from item in data group item.Value by item.Key into groupItem select new { Key = groupItem.Key, Sum = groupItem.Sum() };

 

关于orderby和join还有let子句在此略过。

 

写在后面

非常值得注意的一点是LINQ Expression不等于SQL,只是看起来相似的两个东西。不同语言的LINQ Expression也都有自己的特色,C#的LINQ Expression就更类似于函数式,而VB的则更接近于自然语言。不过LINQ Expression与SQL之间还有一个最显著的区别就是他们所查询的目标是不同的,LINQ Expression查询的总的来说还是一个对象集合,而SQL面向的是一个关系。所以在LINQ Expression中,对象是主体,但与之对应的记录(Record,或行Row)在SQL表达式中则几乎无法体现,因为在纯关系中,有序N元组本来就没多少意义。这一点使得LINQ有强大于SQL的地方,也使得LINQ不能实现SQL所有的功能。

 

现在我们可以来谈谈为什么在本系列第一篇文章为什么要着力于一个晦涩难懂的抽象概念:“关系”。这是因为它是LINQ与SQL的唯一联系。LINQ Expression和SQL Expression是完全不同的两个东西,唯一相同的地方就是它们都是描述面向关系的查询的。关系是一个抽象的概念,在.NET Framework中,它被表现为一个强类型的枚举器(IEnumerable<T>),在关系型数据库中,它被表现为一个二维表。