本篇将是关于LINQ Operators的最后一篇,包括:集合运算符(Set Operators)、Zip操作符、转换方法(Conversion Methods)、生成器方法(Generation Methods)。集合运算符用语对两个sequence进行操作;Zip运算符同步遍历两个sequence(像一个拉链一样),返回的sequence基于在每一个元素对上应用lambda表达式;转换方法用来将实现了IEnumerable的sequence转换到其他类型的集合,或从其他类型的集合转换到sequence;生成器方法/Generation Methods用来创建简单的本地sequence。
集合运算符/Set Operators
IEnumerable, IEnumerable→IEnumerable
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 = { 1, 2, 3 }, seq2 = { 3, 4, 5 };<br> IEnumerable<int> concat = seq1.Concat(seq2); // { 1, 2, 3, 3, 4, 5 }<br> IEnumerable<int> union = seq1.Union(seq2); // { 1, 2, 3, 4, 5 }
复制代码 如果两个sequence的类型不同但是他们的元素共享同一个基类型时,我们通常明确指定类型参数。比如,在使用反射API时,方法和属性分别用MethodInfo和PropertyInfo类型表示,他们的共同基类是 MemberInfo。我们可以在调用Concat时明确指定基类型来连接方法和属性:- MethodInfo[] methods = typeof(string).GetMethods();<br> PropertyInfo[] props = typeof(string).GetProperties();<br> IEnumerable<MemberInfo> both = methods.Concat<MemberInfo>(props);
复制代码 下面的示例中,我们在连接之前先对Methods进行了过滤:- var methods = typeof(string).GetMethods().Where(m => !m.IsSpecialName);<br> var props = typeof(string).GetProperties();<br> var both = methods.Concat<MemberInfo>(props);
复制代码 有意思的是,这个示例可以在C# 4.0中编译但不能在C# 3.0中编译,因为它依赖于接口类型参数协变:methods的类型是IEnumerable,在其转换为IEnumerable时需要C# 4.0提供的协变功能。这个例子很好的说明了协变如何让事情按我们期望的方式来工作。
Intersect和Except
Intersect返回两个sequence中都存在的元素;Except返回存在于第一个sequence而不存在于第二个sequence中的元素:- int[] seq1 = { 1, 2, 3 }, seq2 = { 3, 4, 5 };<br> IEnumerable<int><br> commonality = seq1.Intersect(seq2), // { 3 }<br> difference1 = seq1.Except(seq2), // { 1, 2 }<br> difference2 = seq2.Except(seq1); // { 4, 5 }
复制代码 Enumerable.Except的内部工作方式是:把第一个sequence中的所有元素装载到一个dictionary中,然后从这个dictionary中移除所有存在于第二个sequence中的元素。它等价于SQL中的NOT EXISTS或NOT IN子查询:- SELECT number FROM numbers1Table<br> WHERE number NOT IN (SELECT number FROM numbers2Table)
复制代码
The Zip Operator
IEnumerable, IEnumerable→IEnumerable
Zip运算符在Framework 4.0被添加进来。它同步遍历两个sequence(像一个拉链一样),返回的sequence基于在每一个元素对上应用lambda表达式。如下例:- int[] numbers = { 3, 5, 7 };<br> string[] words = { "three", "five", "seven", "ignored" };<br> IEnumerable<string> zip = numbers.Zip (words, (n, w) => n + "=" + w);<br> <br> // 产生的sequence包含如下元素<br> // 3=three<br> // 5=five<br> // 7=seven
复制代码 任何输入sequence中额外的元素(不能组成元素对)会被忽略。Zip运算符在查询数据库时不被支持。
转换方法/Conversion Methods
LINQ主要用来处理sequences:即IEnumerable类型的集合。 转换方法用来将sequence转换到其他类型的集合,或从其他类型的集合转换到sequence。
方法
| 说明
| OfType
| 把IEnumerable转换到IEnumerable,丢弃错误的类型元素
| Cast
| 把IEnumerable转换到IEnumerable,如果存在错误的类型元素则抛出异常
| ToArray
| 把IEnumerable转换到T[]
| ToList
| 把IEnumerable转换到List
| ToDictionary
| 把IEnumerable转换到Dictionary
| ToLookup
| 把IEnumerable转换到ILookup
| AsEnumerable
| 向下转换到IEnumerable
| AsQueryable
| 转换到IQueryable
| OfType和Cast
OfType和Cast接收一个非泛型的IEnumerable集合,返回一个泛型的IEnumerable,这样我们就可以对其进行查询了。- ArrayList classicList = new ArrayList(); // in System.Collections<br> classicList.AddRange(new int[] { 3, 4, 5 });<br> IEnumerable<int> sequence1 = classicList.Cast<int>();
复制代码 Cast和OfType的不同行为表现在当遇到一个类型不兼容的输入元素时,Cast抛出一个异常,而OfType忽略不兼容的元素。继续上一个示例:- DateTime offender = DateTime.Now;<br> classicList.Add(offender);<br> IEnumerable<int><br> sequence2 = classicList.OfType<int>(), // OK - ignores offending DateTime<br> sequence3 = classicList.Cast<int>(); // Throws exception
复制代码 判断元素类型是否兼容的规则与C#的is操作符一致,我们可以通过OfType的内部实现来进行验证:- public static IEnumerable<TSource> OfType<TSource>(IEnumerable source)<br> {<br> foreach (object element in source)<br> if (element is TSource)<br> yield return (TSource)element;<br> }
复制代码 Cast具有相同的实现,除了它里面省略了类型兼容性的检测:- public static IEnumerable<TSource> Cast<TSource>(IEnumerable source)<br> {<br> foreach (object element in source)<br> yield return (TSource)element;<br> }
复制代码 这些实现的一个结果是我们无法使用Cast来进行数值或定制的转换(对这些我们必须使用Select运算符)。换句话说,Cast不具备C#中cast操作符的灵活性:- int i = 3;<br> long l = i; // 隐式数值转换 int->long<br> int i2 = (int)l; // 显式数值转换 long->int
复制代码 现在我们再来看看OfType和Cast转换int sequence到long sequence的行为:- int[] integers = { 1, 2, 3 };<br> IEnumerable<long> test1 = integers.OfType<long>();<br> 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 = 123;<br> object element = value;<br> long result = (long)element; // exception
复制代码 正如我们前面的建议,此问题的解决方案是使用Select:- IEnumerable<long> castLong = integers.Select(s => (long)s);
复制代码 当我们需要把一个泛型的输入sequence中的元素向下转换(转为更具体的类型)时,OfType和Cast也会非常有用。比如,如果我们有一个IEnumerable的输入sequence,OfType仅返回apples,这在我们稍后会讲到的LINQ to XML中会特别有效。
Cast有对应的查询表达式语法支持:只需在范围变量之前指定一个类型即可:- var query =<br> from TreeNode node in myTreeView.Nodes<br> ...
复制代码
ToArray, ToList, ToDictionary, 和ToLookup
ToArray和ToList分别把结果输出到一个数组或泛型列表。这些运算符会强制对输入sequence进行立即遍历(除非间接通过子查询或表达式树)。关于他们的示例请参考:LINQ之路 6:延迟执行(Deferred Execution)
ToDictionary和ToLookup接受如下参数:
参数
| 类型
| 输入sequence / Input sequence
| IEnumerable
| 键选择器 / Key selector
| TSource => TKey
| 元素选择器 / Element selector(optional)
| TSource => TElement
| 比较器 / Comparer (optional)
| IEqualityComparer
| ToDictionary也会强制sequence的立即执行,并把结果写入一个泛型Dictionary。提供的键选择器表达式必须为每个元素产生唯一的键值,否则将会抛出异常。而ToLookup允许多个元素使用同一个键值。对于lookups我们已经在LINQ之路13:LINQ Operators之连接(Joining)中有过详细介绍。
AsEnumerable和AsQueryable
AsEnumerable把一个sequence向上转换到IEnumerable,强制编译器把后来的查询运算符绑定到Enumerable类中的方法,而不是Queryable类的方法。他们的示例请参考LINQ之路8:解释查询(Interpreted Queries)中的“组合使用解释查询和本地查询”一节。
AsQueryable把一个sequence向下转换到IQueryable(如果它实现了该接口)。否则,它会实例化一个IQueryable包装器用于本地查询之上。
生成器方法/Generation Methods
void→IEnumerable
Operator
| 说明
| Empty
| 创建一个空sequence
| Repeat
| 创建一个含有重复elements的sequence
| Range
| 创建一个包含指定整数的sequence
| Empty, Repeat, 和Range是Enumerable类的静态方法,而不是扩展方法,用来创建简单的本地sequence。
Empty
Empty创建一个空的sequence,它只需要一个类型参数:- foreach (string s in Enumerable.Empty<string>())<br> Console.Write(s); // <nothing>
复制代码 通过配合使用??操作符,Empty可以完成DefaultIfEmpty相反的操作。比如,我们有一个交错整形数组,现在我们想要把所有的整数放到一个简单的平展列表中。下面的SelectMany查询在遇到一个空的内部数组时会失败:- int[][] numbers =<br> {<br> new int[] { 1, 2, 3 },<br> new int[] { 4, 5, 6 },<br> null // this null makes the query below fail.<br> };<br> IEnumerable<int> flat = numbers.SelectMany(innerArray => innerArray);
复制代码 Empty搭配??就可以解决该问题:- IEnumerable<int> flat = numbers<br> .SelectMany(innerArray => innerArray ?? Enumerable.Empty<int>());<br> foreach (int i in flat)<br> Console.Write(i + ""); // 1 2 3 4 5 6
复制代码
Range 和Repeat
Range和Repeat只能用来操作integers。Range接受一个起始索引和元素个数:- foreach (int i in Enumerable.Range(5, 5))<br> Console.Write(i + ""); // 5 6 7 8 9
复制代码 Repeat接受重复的数值,和该数值重复的次数:- foreach (int i in Enumerable.Repeat(5, 3))<br> Console.Write(i + ""); // 5 5 5
复制代码
通过六篇博客的篇幅,我们对于LINQ Operators的介绍终于告一段落了,我也长舒了一口气。一方面希望给阅者最全面最详细的介绍,另一方面又怕再不结束LINQ Operators,只怕阅者都会产生审美疲劳了,^_^!
关于LINQ,我们还有一块没有介绍,它就是LINQ to XML,我准备用3-4篇左右的篇幅来对它进行讨论,等到LINQ to XML完成的那一天,LINQ之路系列博客也就大功告成了。希望广大博友继续给我支持,给我力量,谢谢。^_^
系列博客导航:
LINQ之路系列博客导航
LINQ之路 1:LINQ介绍
LINQ之路 2:C# 3.0的语言功能(上)
LINQ之路 3:C# 3.0的语言功能(下)
LINQ之路 4:LINQ方法语法
LINQ之路 5:LINQ查询表达式
LINQ之路 6:延迟执行(Deferred Execution)
LINQ之路 7:子查询、创建策略和数据转换
LINQ之路 8:解释查询(Interpreted Queries)
LINQ之路 9:LINQ to SQL 和 Entity Framework(上)
LINQ之路10:LINQ to SQL 和 Entity Framework(下)
LINQ之路11:LINQ Operators之过滤(Filtering)
LINQ之路12:LINQ Operators之数据转换(Projecting)
LINQ之路13:LINQ Operators之连接(Joining)
LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)
LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法
LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法
LINQ之路17:LINQ to XML之X-DOM介绍
LINQ之路18:LINQ to XML之导航和查询
LINQ之路19:LINQ to XML之X-DOM更新、和Value属性交互
LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces
LINQ之路21:LINQ to XML之生成X-DOM(Projecting)
LINQ之路系列博客后记
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |