数据查询

处理数据是编程的一大任务。其中对字符串数据处理尤其重要,本篇略过字符串处理,只谈linq、foreach、标准查询运算符。

一、foreach

C#支持foreach迭代数据,和传统的for循环很类似,并且比for循环更易用。如:

foreach (var a in 数据源) { console.WriteLine(a) ;}

而for循环需要定义一个下标: for (int i = 0; i < 数据源.Length; i++) console.WriteLine (数据源[i]);

可见foreach更加简易。但是应该知道,for循环并非专门用来处理数据,它只是一个知道循环次数,然后不断执行循环体的基本程序结构。for 循环可以执行任何类型的编程任务;而foreach则是专程定制来迭代数据的。

foreach 要求数据源实现IEnumerable<T>接口(或非泛型版本),为何?

foreach 的工作机制类似:

IEnumerator<T> s = ((IEnumerable<T>) 数据源).GetEnumerator();

while (s.MoveNext()) { console.WriteLine(s.Current); }

首先,IEnumerable 并不处理实际的迭代工作,而是需要IEnumerator配合,IEnumerator有三个成员:

T Current //当前元素

bool MoveNext() //迭代到下一个位置

void Reset()  //复原位置

注意,一开始的位置是在第一个元素之前,所以先MoveNext再调用Current才是第一个元素。当迭代超过尾端,MoveNext返回flase ,Current 无效。IEnumerator 可能引发异常 InvalidOperationException (无效操作异常)。

表面上看,foreach 的限制很大,需要数据源实现两个接口,但是.net大部分内置数据源,如数组,列表,集合等都实现了这两个接口,因此foreach 的可用性很高。

二、yield

既然我们知道数据源需要实现IEnumerable接口才能被强大的foreach迭代所用,那么第二步就是想办法让我们自定义的数据源支持该接口。可以用两个方法,第一个是对支持IEnumerable的数据源做一个简单的包装,如内置一个数组存放数据。第二个从零开始建造自己的数据源。

其中,你可以按部就班的实现接口的每一个函数和属性,但是c#提供了更简易的方法,那就是利用 yield 关键字直接生成IEnumerable实例。

IEnumerable create(int start, int end){ while (start < end) yield return start++; }

包含 yield 关键字的函数内部会产生一个IEnumerable对象,或者IEnmerator对象(视返回类型而定)用以返回。这个临时对象记录相关的位置信息,效果如同手工编写IEnumerator 实现类并生成对象。

yield return 语句并非是函数的返回,不要和return语句混淆,yield return 产生一个记录点,暂停当前函数的执行,并把当前结果返回,当下次迭代时(即调用IEnumerator.MoveNext() 方法),从该记录点后继续执行。直到函数执行结束或者遇到 yield break 语句。

如 while ( start < end) if (start == 100) yield break; yield return start++; }

yield 关键字很强大,背后的生成机制很神奇,但是yield生成的迭代对象还是有点不足。第一,不支持Reset() 复原位置,第二,我会感觉这个方案是临时性的。

三、linq

当你实现了数据源,调用foreach就能迭代该数据源,貌似一切问题都完结了。其实编程中有很多任务需要对数据源进行再加工,linq就是这种工具。它支持筛选,排序,生成新序列等,也就是等于将原序列映射到新的序列中,而得到的结果序列就能利用foreach继续执行任务。

(一)语法:

linq包含的基本子句为:from、where、select、group、orderby、join

定义变量子句:into、let

辅助关键字:in、on、equals、by、ascending、descending

1、linq表达式语法: linq 表达式从from 子句开头,group 或者 select子句结尾,中间可包含任何子句。

2、from语法: from 变量 in 数据源(IEnumerable<T>类型 或 IQueryable<T>类型)

作用:引入上下文变量名,表示当前迭代元素,有点类似foreach (var 变量 in 数据源)的作用。

from x in A  from y in B 这样的结构等于双重迭代,优化的策略是找出B和A的关联,如B = x.b,即可以缩小迭代次数。

3、select 语法: select 表达式

表达式的返回结果就是元素的类型,即将以上迭代最终的成果通过表达式映射到最终序列。

4、group 语法: group 表达式 by 键

键组结构的序列

5、orderby语法:orderby 键,第二键(可选)… ascending(升序、可选) 或 descending(降序)

根据键排序结果

6、join 语法: join 变量 in 数据源 on 左键 equals 右键 into(可选) 组变量

将左集合和右集合通过键关联,如果有into部分,右集匹配部分就是一组数据,而不是单个数据。

后续上下文是变量还是组变量,全看是否有into部分。

7、into 语法:

group …into 变量

select …into 变量

join … into 变量

以上三种变量类似 from x in A 中的x,那么变量对应的数据源A分别就是:键组结构的序列、select 指定类型的序列、

按键分组,各分组组成的序列(即序列组成的序列)。

如:

from T x in A join U y in B on y.a equals x into K group new {x,k} by x into g select g.key into m where m < 10 select m;

相当于:

from g in (from T x in A join U y in B on y.a equals x into K group new {x,k} by x)

from m in (from T x in A join U y in B on y.a equals x into K group new {x,k} by x into g select g.key)

from K in (from T x in A join U y in B on y.a equals x group y by x into g select (from y in g select y))

在group和select 后续定义的into 变量是为了对结果附加操作;而在join之后附加into 定义变量,后续得到左集匹配的一组数据,比一一对应更适合某些情形。

8、let 语法:let 变量 = 表达式

简化表达式的书写,构建中间变量。

9、where 语法: where 条件表达式

根据条件表达式筛选元素,得到序列的子集

(二)转换到标准查询运算符

linq易于理解,但是有时候还需要依赖标准查询运算符进行更细致的操作。这个时候我觉得就需要弄懂linq是怎么转换到标准查询运算符的。

from 引入数据源,是标准的linq抬头,而标准查询运算符是扩展函数,直接应用到序列点运算符之后,因此自然就知道处理的是哪个序列。

如: from x in A  ==> A.

x 是范围变量,表示迭代中的元素,而扩展函数对应的是接收传入的委托参数。

如: from x in A select  x ==> A.Select( x =>x )

多重from的情况:

from x in A from y in B select x+y ==> A.SelectMany(x=>B, (x,y)=>x+y )

from x in A from y in B select y ==> A.SelectMany( x=>B )

from x in A from y in B select x ==> A.SelectMany( x=>B, (x,y)=>x )

from x in A from y in B from k in C select x+y+k ==>

A.SelectMany( x=>B, (x,y)=>C.Select(k=>x+y+k)).SelectMany(k=>k)

以上是我想到的方案,如果用一下串联的方法虽然更加易于理解,但是会丢失掉上一级的元素。

如:A.SelectMany(x=>B).SelectMany(y=>C, (y,k)=> y+k+x(x无法访问))

不过我反编译后发现编译器真的是通过这种方式实现的:

A.SelectMany(x=>B, (x,y)=>new{x,y})

.SelectMany(xy=>C,(xy,k)=>xy.x+xy.y+k);

 

例子2:from x in A  where x==1 select x ==> A.Where(x=>x==1)

from x in A where x>1 select 1 ==> A.Where(x=>x>1).Select(x=>1)

from x in A where x>1 select x into y where y<10 select y ==> A.Where(x=>x>1).Select(x=>x).Where(x=>x<10)

from x in A from y in B where x>10 && y <3 select new {x,y} ==>

A.SelectMany(x=>B, (x,y)=>new {x,y}).Where(xy=>xy.x > 10 && xy.y < 3).Select(xy=>new {x= xy.x, y=xy.y})

和linq语法不同,Where函数返回的是序列,因此可以和Select按任意顺序串联起来,并且,如果最终结果是当前元素组成的序列,那么也不必非要附带Select结尾。而 linq强制要求from 开始 select结尾。

 

例子3:from x in A orderby x%3 descending, x%2 select x ==> A.OrderByDescending(x=>x%3).ThenBy(x=>x%2)

函数语法通过OrderBy 或 OrderByDescending 起始, ThenBy 或 ThenByDescending 做后续处理基于多个条件的排序。

例子4:from x in A join y in B on x equals y.a select new {x,y} ==> A.Join(B, x=>x, y=>y.a, (x,y)=>new {x,y})

from x in A join y in B on x equals y.a into C select new {x,C} ==>A.GroupJoin(B,x=>x,y=>y.a,(x,C)=>new {x,C})

例子5:from x in A from y in B group y by x ==>

A.SelectMany(x=>B, (x,y)=>new {x,y}).GroupBy(xy=>xy.x, xy=>xy.y)

 

四、标准查询运算符

排序:

排序不改变元素构成,只改变元素顺序。

函数

linq

OrderBy( Func<source, key> ) orderby 键选择表达式
OrderByDescending 降序版 orderby key descending
OrederBy( Func<source,key>, Icomparer<key> )  
降序版  
ThenBy  后续排序 orderby key1,key2(多个) …
ThenByDescending 降序版本 orderby key1,key2(多个)… descending
带比较器版本  
带比较器版本  
Reverse 颠倒顺序  

集合运算:

返回子集或并集

函数

linq

Distinct 移除重复元素  
Distinct(  IEqualityComparer(T) )  
Except( rSource ) 两集合之差  
带相等比较器版本  
Intersect 两集合之交  
带相等比较器版本  
Union 两集合之并  
带相等比较器版本  

筛选:

筛选并返回符合条件的子集

函数

linq

OfType 返回特定类型元素序列  
Where ( Func<source, bool> ) where 条件表达式

判定:

判断序列是否符合条件(返回bool 单值)

函数

linq

All (Func<source, bool> ) 所有元素满足指定条件  
Any 是否有元素  
Any (Func<source,bool>)是否有符合条件的元素  
Contains( T ) 是否有指定元素  
Containz( T, IEqualityComparer<T>) 带相等比较器版本  

映射:

将原序列映射到新生成的序列

函数

linq

Select( Func<source, T> ) 转换序列 from..in(source)…select  T
Select( Func<source, int, T> )带位置的选择器版本  
SelectMany( Func<source, IEnumerable<U>> )  将序列转换为可枚举元素,并串联每个元素的枚举结果 from ..in (source) from..in (IEnumerable<U>)(多个)… select U
SelectMany 带位置的选择器版本  
SelectMany( Func<source, IEnumerable<U>>, Func<source, U, result > from..in(source) from..in(IEnumerable<U>)(多个)… select result
SelectMany( Func<source, int, IEnumerable<U>>, Func<source,U,result>带位置的选择器版本  

分段:

展示数据的时候,经常需要分页(分段)显示,一次显示一小段便于用户查看。

函数

linq

Skip(int)  跳过前n个元素  
SkipWhile(Func<source, bool>) 跳过符合条件的前n个元素  
SkipWhile( Func<source, int, bool>) 带位置版  
Take(int) 返回前n个元素  
Take(Func<source,bool>) 返回满足条件的前n个元素  
Take(Func<source,int,bool>) 带位置版  

联接:

联接操作是将左集合和右集合的元素进行匹配。匹配的意义是:一、一次匹配等于一次迭代结果,不匹配就没有结果,最大化是左序列长度*右序列长度。二、上下文可以访问匹配的相关元素。三,可映射到新的序列。

如左集和右集匹配10次,就需要10次迭代该结果,每一次,都能访问这次迭代匹配的左集元素和右集合元素(即上下文)。

联接结果有:

左外部:即无匹配的左集合部分+交集

右外部:和左外部原理差不多

内部:即交集

全联接:即左未匹配部分+交集+右未匹配部分

术语解释:

同等联接:即基于键相等的联接

非同等联接:即基于其他条件的匹配。

交叉联接:左集每个元素和右集合所有元素匹配,即左集X右集。

函数

linq

Join( IEnumerable<right>, Func<source, key>,
Func<right, key>, Func<source, right, result>)右序列,左序列键选择器,右序列键选择器,匹配结果转换器。联接的结果是左序列一个元素和它匹配的右序列一个元素。
join …in (right) on key1 equals key2
select result
带相等比较器版本  
GroupJoin( right, Func<source, key>, Func<right, key>, Func<source, IEnumerable<right>, result> )右序列,左序列键选择器,右序列键选择器,匹配结果转换器。分组联接的结果是左序列一个元素和它匹配的一组右序列元素 jion …in (right) on key1 equals key2 into (IEnumerable<right>)
select result
带相等比较器版本  

分组:

按照指定键分组序列元素,结果映射为 IGrouping<key,element> 或 ILookup<key, element>类型的键组结构的元素序列。

分组操作:前提是一组序列,结果是键组结构的序列。

集合操作:前提是一组或两组同类序列(通过值比较),结果是原类型元素序列的子集或者并集。

联接操作:前提是两组序列(通过键关联),结果是匹配组对或一对多组对(但并不形成键组结构实体),然后映射到指定类型序列。

函数

linq

GroupBy(Func<source,key>) group T by key
GroupBy(Func<source,key>, IEqualityComparer<key>) 带相等比较器版本  
GroupBy(Func<source,key>, Func<source, result>) 分组并映射(元素版) group result by key
带相等比较器版本  
GroupBy(Func<source,key>, Func<key,IEnumerable<source>, result>) 分组并映射(键组版)  
带相等比较器版本  
GroupBy(Func<source,key>,Func<source,U>,Func<key, IEnumerable<U>, result>) 键选择器,元素选择器, 结果转换器(键组版)  
带相等比较器版本  
ToLookup(Func<source,key>) 映射到 ILookup<key,element>键组结构的序列  
带相等比较器版本  
ToLookup(Func<source,key>, Func<source, U>)键选择器,元素选择器  
带比较器版本  

创建:

构建特定类型的序列。

函数

linq

DefaultIfEmpty  如果集合空即创建一个默认元素的序列  
DefaultIfEmpty(T ) 指定值版  
Empty 创建空集  
Range(int start,int count) 创建从start开始到start+count –1 结束的递增整数序列。  
Repeat(T e, int count) 创建count个e 组成的序列  

比较:

函数

linq

SequenceEqual(IEnumerable<T>) 比较两序列是否相同  
带相等比较器版本  

定位元素:

定位单个符合条件的元素。

函数

linq

ElementAt(int index) 返回指定位置的元素  
ElementAtOrDefault(int) 超出范围返回默认值版本  
First 返回第一个元素  
FirstOrDefault 找不到返回默认值版本  
First(Func<source,bool>) 返回符合条件的第一个元素  
FirstOrDefault 找不到返回默认值版本  
Last 返回最后一个元素  
LastOrDefault 找不到返回默认值版本  
Last(Func<source,bool>) 返回符合条件的最后一个元素  
LastOrDefault 找不到返回默认值版本  
Single 返回序列中唯一一个元素,如非唯一元素或找不到引发InvalidOperationException异常  
SingleOrDefault 找不到返回默认值版本  
Single(Func<source,bool>)返回序列中唯一符合条件的元素,如非唯一或找不到,引发异常  
SingleOrDefault 找不到返回默认值版本  

转换:

将序列转换到特定类型的新序列。

函数

linq

AsEnumerable 调用非自定义实现  
AsQueryable 类似,IQueryable版  
Cast<T> 强制转换成指定类型序列 from T element in 数据源
OfType<T> 将能转换为指定类型的元素组成序列  
ToArray 转换成数组  
ToDictionary (Func<source,key>) 根据键转换成字典序列  
带相等比较器版本  
ToDictionary(Func<source,key>,Func<source,element>) 键选择器,元素选择器  
带相等比较器版本  
ToList 转换成列表  
ToLookup 转换为ILookup<key,element>键组结构的序列 (请参照分组小节)  

串联:

将两序列首尾串联,合并成一个新的序列。

串联和并集的差别是它并不比较元素,只简单合并。

函数

linq

Concat(IEnumerable<T>) 串联两序列  

整体运算:

遍历整个序列得出想要的结果。

函数

linq

Aggregate (Func<T 累积值, T 当前元素, T>) 累积操作。每次迭代利用上一次累积的值和当前元素计算结果。  
Aggregate( U 初始值, Func<U, source, U>) 累积器类型不同的版本  
Aggregate( U 初始值,Func<U,source,U>, Func<U, result>) 累加器类型和最终结果类型不同的版本  
Average 平均值  
带转换器版本,应用于非数值序列  
Count 元素个数  
Count( Func<source,bool> ) 符合条件的元素个数。  
LongCount  大小是Int64版本  
带判断器版本  
Max 查找最大值  
带转换器版本  
Min 查找最小值  
带转换器版本  
Sum 求总和  
带转换器版本  
posted @ 2012-12-14 11:38  诺贝尔  阅读(2060)  评论(0编辑  收藏  举报