本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators)、Zip操作符、转换方法(Conversion Methods)、生成器方法(Generation Methods)。集合运算符用语对两个sequence进行操作;Zip运算符同步遍历两个sequence(像一个拉链一样),返回的sequence基于在每一个元素对上应用lambda表达式;转换方法用来将实现了IEnumerable<T>的sequence转换到其他类型的集合,或从其他类型的集合转换到sequence;生成器方法/Generation Methods用来创建简单的本地sequence。

集合运算符/Set Operators

IEnumerable<TSource>, IEnumerable<TSource>→IEnumerable<TSource>

Operator

说明

SQL语义

Concat

连接两个sequences的所有元素

UNION ALL

Union

连接两个sequences的所有元素,但去除重复的元素

UNION

Intersect

返回在两个sequence中都存在的元素

WHERE ... IN (...)

Except

返回存在于第一个sequence而不存在于第二个sequence中的元素

EXCEPT

or

WHERE   ... NOT IN (...)

Concat和Union

Concat返回第一个sequence中的所有元素,后接第二个sequence中的所有元素,即连接两个sequence。Union完成相同的工作,但是会去除重复的元素

            int[] seq1 = { , ,  }, seq2 = { , ,  };
IEnumerable<int> concat = seq1.Concat(seq2); // { 1, 2, 3, 3, 4, 5 }
IEnumerable<int> union = seq1.Union(seq2); // { 1, 2, 3, 4, 5 }

如果两个sequence的类型不同但是他们的元素共享同一个基类型时,我们通常明确指定类型参数。比如,在使用反射API时,方法和属性分别用MethodInfo和PropertyInfo类型表示,他们的共同基类是 MemberInfo。我们可以在调用Concat时明确指定基类型来连接方法和属性:

            MethodInfo[] methods = typeof(string).GetMethods();
PropertyInfo[] props = typeof(string).GetProperties();
IEnumerable<MemberInfo> both = methods.Concat<MemberInfo>(props);

下面的示例中,我们在连接之前先对Methods进行了过滤:

            var methods = typeof(string).GetMethods().Where(m => !m.IsSpecialName);
var props = typeof(string).GetProperties();
var both = methods.Concat<MemberInfo>(props);

有意思的是,这个示例可以在C# 4.0中编译但不能在C# 3.0中编译,因为它依赖于接口类型参数协变:methods的类型是IEnumerable<MethodInfo>,在其转换为IEnumerable<MemberInfo>时需要C# 4.0提供的协变功能。这个例子很好的说明了协变如何让事情按我们期望的方式来工作。

Intersect和Except

Intersect返回两个sequence中都存在的元素;Except返回存在于第一个sequence而不存在于第二个sequence中的元素

            int[] seq1 = { , ,  }, seq2 = { , ,  };
IEnumerable<int>
commonality = seq1.Intersect(seq2), // { 3 }
difference1 = seq1.Except(seq2), // { 1, 2 }
difference2 = seq2.Except(seq1); // { 4, 5 }

Enumerable.Except的内部工作方式是:把第一个sequence中的所有元素装载到一个dictionary中,然后从这个dictionary中移除所有存在于第二个sequence中的元素。它等价于SQL中的NOT EXISTS或NOT IN子查询:

            SELECT number FROM numbers1Table
WHERE number NOT IN (SELECT number FROM numbers2Table)

The Zip Operator

IEnumerable<TFirst>, IEnumerable<TSecond>→IEnumerable<TResult>

Zip运算符在Framework 4.0被添加进来。它同步遍历两个sequence(像一个拉链一样),返回的sequence基于在每一个元素对上应用lambda表达式。如下例:

            int[] numbers = { , ,  };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip (words, (n, w) => n + "=" + w); // 产生的sequence包含如下元素
// 3=three
// 5=five
// 7=seven

任何输入sequence中额外的元素(不能组成元素对)会被忽略。Zip运算符在查询数据库时不被支持。

转换方法/Conversion Methods

LINQ主要用来处理sequences:即IEnumerable<T>类型的集合。 转换方法用来将sequence转换到其他类型的集合,或从其他类型的集合转换到sequence。

方法

说明

OfType

把IEnumerable转换到IEnumerable<T>,丢弃错误的类型元素

Cast

把IEnumerable转换到IEnumerable<T>,如果存在错误的类型元素则抛出异常

ToArray

把IEnumerable<T>转换到T[]

ToList

把IEnumerable<T>转换到List<T>

ToDictionary

把IEnumerable<T>转换到Dictionary<TKey,TValue>

ToLookup

把IEnumerable<T>转换到ILookup<TKey,TElement>

AsEnumerable

向下转换到IEnumerable<T>

AsQueryable

转换到IQueryable<T>

OfType和Cast

OfType和Cast接收一个非泛型的IEnumerable集合,返回一个泛型的IEnumerable<T>,这样我们就可以对其进行查询了。

            ArrayList classicList = new ArrayList(); // in System.Collections
classicList.AddRange(new int[] { , , });
IEnumerable<int> sequence1 = classicList.Cast<int>();

Cast和OfType的不同行为表现在当遇到一个类型不兼容的输入元素时,Cast抛出一个异常,而OfType忽略不兼容的元素。继续上一个示例:

            DateTime offender = DateTime.Now;
classicList.Add(offender);
IEnumerable<int>
sequence2 = classicList.OfType<int>(), // OK - ignores offending DateTime
sequence3 = classicList.Cast<int>(); // Throws exception

判断元素类型是否兼容的规则与C#的is操作符一致,我们可以通过OfType的内部实现来进行验证:

        public static IEnumerable<TSource> OfType<TSource>(IEnumerable source)
{
foreach (object element in source)
if (element is TSource)
yield return (TSource)element;
}

Cast具有相同的实现,除了它里面省略了类型兼容性的检测:

        public static IEnumerable<TSource> Cast<TSource>(IEnumerable source)
{
foreach (object element in source)
yield return (TSource)element;
}

这些实现的一个结果是我们无法使用Cast来进行数值或定制的转换(对这些我们必须使用Select运算符)。换句话说,Cast不具备C#中cast操作符的灵活性:

            int i = ;
long l = i; // 隐式数值转换 int->long
int i2 = (int)l; // 显式数值转换 long->int

现在我们再来看看OfType和Cast转换int sequence到long sequence的行为:

            int[] integers = { , ,  };
IEnumerable<long> test1 = integers.OfType<long>();
IEnumerable<long> test2 = integers.Cast<long>();

当我们遍历结果时,test1产生0个元素的sequence,而test2会抛出异常。我们再次审视OfType的实现,原因显而易见。用int代入TSource之后,下面的表达式(element is long)返回false,因为int和long之间并没有继承关系。

而test2抛出异常的原因就没那么明显了,注意Cast的实现中元素的类型为object。当TSource是一个值类型时, CLR认为这是一个拆箱转换。而拆箱操作要求精确的类型匹配,所以当TSource是一个int时,object-to-long拆箱会失败并抛出异常。可以通过下面的示例进行验证:

            int value = ;
object element = value;
long result = (long)element; // exception

正如我们前面的建议,此问题的解决方案是使用Select:

            IEnumerable<long> castLong = integers.Select(s => (long)s);

当我们需要把一个泛型的输入sequence中的元素向下转换(转为更具体的类型)时,OfType和Cast也会非常有用。比如,如果我们有一个IEnumerable<Fruit>的输入sequence,OfType<Apple>仅返回apples,这在我们稍后会讲到的LINQ to XML中会特别有效。

Cast有对应的查询表达式语法支持:只需在范围变量之前指定一个类型即可:

            var query =
from TreeNode node in myTreeView.Nodes
...

ToArray, ToList, ToDictionary, 和ToLookup

ToArray和ToList分别把结果输出到一个数组或泛型列表。这些运算符会强制对输入sequence进行立即遍历(除非间接通过子查询或表达式树)。关于他们的示例请参考:LINQ之路 6:延迟执行(Deferred Execution)

ToDictionary和ToLookup接受如下参数:

参数

类型

输入sequence / Input sequence

IEnumerable<TSource>

键选择器 / Key selector

TSource => TKey

元素选择器 / Element selector(optional)

TSource => TElement

比较器 / Comparer (optional)

IEqualityComparer<TKey>

ToDictionary也会强制sequence的立即执行,并把结果写入一个泛型Dictionary。提供的键选择器表达式必须为每个元素产生唯一的键值,否则将会抛出异常。而ToLookup允许多个元素使用同一个键值。对于lookups我们已经在LINQ之路13:LINQ Operators之连接(Joining)中有过详细介绍。

AsEnumerable和AsQueryable

AsEnumerable把一个sequence向上转换到IEnumerable<T>,强制编译器把后来的查询运算符绑定到Enumerable类中的方法,而不是Queryable类的方法。他们的示例请参考LINQ之路8:解释查询(Interpreted Queries)中的“组合使用解释查询和本地查询”一节。

AsQueryable把一个sequence向下转换到IQueryable<T>(如果它实现了该接口)。否则,它会实例化一个IQueryable<T>包装器用于本地查询之上。

生成器方法/Generation Methods

void→IEnumerable<TResult>

Operator

说明

Empty

创建一个空sequence

Repeat

创建一个含有重复elements的sequence

Range

创建一个包含指定整数的sequence

Empty, Repeat, 和Range是Enumerable类的静态方法,而不是扩展方法,用来创建简单的本地sequence。

Empty

Empty创建一个空的sequence,它只需要一个类型参数:

            foreach (string s in Enumerable.Empty<string>())
Console.Write(s); // <nothing>

通过配合使用??操作符,Empty可以完成DefaultIfEmpty相反的操作。比如,我们有一个交错整形数组,现在我们想要把所有的整数放到一个简单的平展列表中。下面的SelectMany查询在遇到一个空的内部数组时会失败:

            int[][] numbers =
{
new int[] { , , },
new int[] { , , },
null // this null makes the query below fail.
};
IEnumerable<int> flat = numbers.SelectMany(innerArray => innerArray);

Empty搭配??就可以解决该问题:

            IEnumerable<int> flat = numbers
.SelectMany(innerArray => innerArray ?? Enumerable.Empty<int>());
foreach (int i in flat)
Console.Write(i + ""); // 1 2 3 4 5 6

Range 和Repeat

Range和Repeat只能用来操作integers。Range接受一个起始索引和元素个数:

            foreach (int i in Enumerable.Range(, ))
Console.Write(i + ""); // 5 6 7 8 9

Repeat接受重复的数值,和该数值重复的次数:

            foreach (int i in Enumerable.Repeat(, ))
Console.Write(i + ""); // 5 5 5

通过六篇博客的篇幅,我们对于LINQ Operators的介绍终于告一段落了,我也长舒了一口气。一方面希望给阅者最全面最详细的介绍,另一方面又怕再不结束LINQ Operators,只怕阅者都会产生审美疲劳了,^_^!

关于LINQ,我们还有一块没有介绍,它就是LINQ to XML,我准备用3-4篇左右的篇幅来对它进行讨论,等到LINQ to XML完成的那一天,LINQ之路系列博客也就大功告成了。希望广大博友继续给我支持,给我力量,谢谢。^_^

LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法的更多相关文章

  1. LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

    本篇继续LINQ Operators的介绍,包括元素运算符/Element Operators.集合方法/Aggregation.量词/Quantifiers Methods.元素运算符从一个sequ ...

  2. LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)

    本篇继续LINQ Operators的介绍,这里要讨论的是LINQ中的排序和分组功能.LINQ的排序操作符有:OrderBy, OrderByDescending, ThenBy, 和ThenByDe ...

  3. LINQ之路11:LINQ Operators之过滤(Filtering)

    在本系列博客前面的篇章中,已经对LINQ的作用.C# 3.0为LINQ提供的新特性,还有几种典型的LINQ技术:LINQ to Objects.LINQ to SQL.Entity Framework ...

  4. LINQ之路 7:子查询、创建策略和数据转换

    在前面的系列中,我们已经讨论了LINQ简单查询的大部分特性,了解了LINQ的支持计术和语法形式.至此,我们应该可以创建出大部分相对简单的LINQ查询.在本篇中,除了对前面的知识做个简单的总结,还会介绍 ...

  5. LINQ之路 4:LINQ方法语法

    书写LINQ查询时又两种语法可供选择:方法语法(Fluent Syntax)和查询语法(Query Expression). LINQ方法语法是非常灵活和重要的,我们在这里将描述使用链接查询运算符的方 ...

  6. LINQ之路(3):LINQ扩展

    本篇文章将从三个方面来进行LINQ扩展的阐述:扩展查询操作符.自定义查询操作符和简单模拟LINQ to SQL. 1.扩展查询操作符 在实际的使用过程中,Enumerable或Queryable中的扩 ...

  7. LINQ之路 8: 解释查询(Interpreted Queries)

    LINQ提供了两个平行的架构:针对本地对象集合的本地查询(local queries),以及针对远程数据源的解释查询(Interpreted queries). 在讨论LINQ to SQL等具体技术 ...

  8. [转]LINQ之路系列博客导航

    分享一个学习Linq的好博客:Linq之路

  9. LINQ之路 1: LINQ介绍

    LINQ是.NET Framework 3.5的新特性,其全称是 Language Integrated Query,即语言集成查询,是指将查询功能和语言结合起来.从而为我们提供一种统一的方式,让我们 ...

随机推荐

  1. Express中间件的原理及实现

    在Node开发中免不了要使用框架,比如express.koa.koa2拿使用的最多的express来举例子开发中肯定会用到很多类似于下面的这种代码 var express = require('exp ...

  2. 《JAVA编程思想》第四版 PDF

    感谢,参考:https://www.cnblogs.com/buwuliao/p/8073211.html 一.链接: 中文版: https://pan.baidu.com/s/1d07Kp4 密码: ...

  3. MyBatis映射配置文件详解

    <?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-// ...

  4. Feign 客户端源码解析

    Feign的使用非常简单,增加如下配置之后,便可以使用Feign进行调用.非常简单是不是.主要的工作由Feign框架完成.业务代码只提供了一个Interface, 然后由Feign动态生成代理类来实现 ...

  5. [本体论][UML][统一建模语言][软件建模][OWL]从本体论到UML到OWL

    以下内容,是关于软件建模的方法与思路. UML与OWL都是基于本体论的建模语言. 本体论(哲学) 本体论(信息科学) UML(统一建模语言) more info 参考:[设计语言][统一建模语言][软 ...

  6. Docker入门6------生产力工具rancher

    rancher的安装: https://blog.csdn.net/wh211212/article/details/80932264 安装 一行命令就可以安装 sudo docker run -d ...

  7. vue中富文本编辑框

    .npm install vue-quill-editor --save .npm install quill --save .封装成子组件 <template> <quill-ed ...

  8. 插入排序(Python实现)

    目录 1. for版本--插入排序 2. while版本--插入排序 3. 测试用例 4. 算法时间复杂度分析 1. for版本--插入排序 def insert_sort_for(a_list): ...

  9. Python记录12:迭代器+生成器+生成式

    '''1. 什么是迭代器 什么是迭代:迭代就是一个重复的过程,但是每一次重复都是基于上一次的结果而进行的 单纯的重复不是迭代: while True: print(1) 迭代的过程 l=['a','b ...

  10. CentOS 7 源码搭建LNMP环境

    搭建 LNMP 环境 源码包版本 :  CentOS Linux  7 nginx-1.15.1.tar.gz  mysql-boost-5.7.21.tar.gz  php-7.2.7.tar.gz ...