• 数据查询


    处理数据是编程的一大任务。其中对字符串数据处理尤其重要,本篇略过字符串处理,只谈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 求总和  
    带转换器版本  
  • 相关阅读:
    LINUX下 my.cnf php.ini的位置
    如何启动/停止/重启MySQL
    Windows下Git Bash中文乱码
    linux下忘记密码怎么办,如何重置密码
    如何在word里面插入目录
    git常见问题解决办法
    git配置global信息
    怎样把excel一列分成多列
    php中array_flip数组翻转
    笔记本电脑键盘字母和字母错乱怎样解决
  • 原文地址:https://www.cnblogs.com/Nobel/p/2817749.html
Copyright © 2020-2023  润新知