LINQ首部曲: LINQ To Object Part 2 - Using VB.NET

 
LINQ首部曲: LINQ To Object Part 2 - Using VB.NET
 
文/黄忠成
 
 续前文,这一篇一样是发表于Run! PC杂志,同时收录于【极意之道-.NET Framework 3.5资料库开发圣典-ASP.NET篇】一书中,于此我针对VB.NET做了调整。
 
回到起点: LINQ 语法规则
 
 任何语言皆有其基本语法规则存在,LINQ虽然被打造成一个与编译器无关的Framework,但为了让设计师能更方便、更直觉的使用LINQ Expression,目前已知的C#、VB.NET都定义了一套专属的LINQ Expression语法,虽然各个编译器所提供的语法略有差异,但基本上差距不远,本节以VB.NET所提供的LINQ Expression为基准,为读者们介绍LINQ Expression的语法规则及各式各样的应用,表1是笔者所整理出来的VB.NET LINQ Expression语法规则。
[表1]
u   query-expression
 From <alias> In <source|query-expression> <<where-expression>|<group-by expression>|<join expression>|<order-by-expression>> <select-expression>
u   where-expression
 left-variable <operand> right-variable
n   operand
(= | <> | < | > | >= | <=)
 left-variable <operand> query-expression(single value result)
 query-expression(single value result)   <operand> left-variable
query-expression(single value result)   <operand> query-expression(single value result)
 variable.<function> (Boolean result)
 variable.<function> operand right-variable
 variable.<function> operand variable<function>
 <shared function> operand right-varaible
 <shared function> operand <shared function>
 <function> operand right-varaible
 <function> operand <shared function>
 <VB.NET Expression single line> (Boolean result)
 <VB.NET Expression single line> operand right-variable
 <VB.NET Expression single line> operand <VB.NET Expression single line>
 where-expression < And | Or | AndAlso | OrElse>, where-expression <*>
u   group-by expression
 group <alias | query-expression> by <alias.member|query-expression(single value result)> into <variable>
group <alias | query-expression> by <alias.member| query-expression(single value result)>,<alias.member| query-expression(single value result)> into <variable>
u   join-expression
 join <alias > in <source|queryt-expression> on <where-expression(equal only)>
 join <alias > in <source|queryt-expression> on <where-expression(equal only)> into <variable>
u   order-by-expression
 order by <alias-member> <asc|desc>
 order by <alias-member>,<alias-member> <asc|desc>
u   select-expression
 Select <alias member>
 Select new With {anonymous type declaration}
乍看之下,此表似乎相当复杂,其实她还蛮简单的,以此表搭配下文的语法分类解说,应能让读者们快速的掌握VB.NET中的LINQ Expression语法。
 
Query-Expression
 
 LINQ Expression中至少会包含一个query-expression,表一中对query-expression的定义如下。
From <alias> In <source|query-expression> <<where-expression>|<group-by expression>|<join expression>|<order-by-expression>> <select-expression>
<alias>指的是欲在此query-expression中使用的别名,更明确说是一个变数,编译器会将In后面的运算式所产生的结果放到此变数中,此处有一点必须特别指出,In后面的<source|query-exprssion>代表著来源,<soruce>可以是一个IEnumerable(Of T)类型的物件,法则上<source>也可以由另一个<query-exprssion>所取代,我们将此称为Nested query-expression(巢状Query)。Query-expression中可以包含Where语句<where-expression>、Group By语句<group expression>、Join语句<join expression>、Order By修饰句<order-by-exprssion>及Select语句<select-expression>,因应不同的语句,其内或允许query-exprssion的出现,下例是一个简单的query-expression。
Dim list() As String = {"1111", "2222", "3333"}
Dim p = From o In list Select o
 
Where-expression
 
 Query-expression中可以包含where-expression,用来对物件作查询动作,下例是一个简单的例子。
Dim list() As String = {"1111", "2222", "3333"}
Dim p = From o In list Where o = "2222" Select o
如表一所示,where-expression的left-expression及 right-expression也可以由query-expression所取代,前提是这个query-expression必须传回单一值,如程式1。
[程式1]
Sub TestComplexWhere2()
        Dim p1() = { _
                     New With {.Name = "code6421", .Address = "Taipai"}, _
                     New With {.Name = "tom", .Address = "Taipai"}, _
                     New With {.Name = "jeffray", .Address = "NY"} _
                   }
        Dim p2() = { _
                     New With {.Name = "code6421", .Title = "Manager"}, _
                     New With {.Name = "tom", .Title = "Director"}, _
                     New With {.Name = "jeffray", .Title = "Programmer"} _
                   }
        Dim p3() = { _
                   New With {.Name = "code6421", .Hand = "Manager", .Address = "Taipai"}, _
                   New With {.Name = "tom", .Hand = "Director", .Address = "Taipai"}, _
                   New With {.Name = "jeffray", .Hand = "Programmer", .Address = "Taipai"} _
                   }
 
        Dim result = From s In p2  _
         Where (From s1 In p1 Where s1.Name = s.Name Select s1.Address).ToArray()(0) = _
         (From s2 In p3 Where s2.Name = s.Name Select s2.Address).ToArray()(0) Select s
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
ToArray函式是LINQ To Object Framework所提供的函式,用途是将query-expression的结果转成阵列,一旦转成阵列后就可以透过()阵列元素存取值去取得单值。
当然,基于LINQ Expression的转译规则,你也可以使用传回Boolean值的函式来协助比对。
Sub TestWhereWithFunction()
    Dim list() As String = {"code6421", "tom", "cathy"}
    Dim result = From s1 In list Where MyExpressionFunc(s1) Select s1
End Sub
 
Function MyExpressionFunc(ByVal s As String) As Boolean
     Return IIf(s = "code6421", True, False)
End Function
这个函式可以是成员函式(需要有物件、静态函式,或是位于Module中的函式皆可。
 
Group-expression
 
 Query-expression中允许含有Group-expression,用于将资料分类用,如程式2。
[程式2]
Sub TestGroupByLinq()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = From o In persons Group o By o.Address Into g = Group _
              Select New With {.Address = Address, .Persons = g}
        For Each item In result
            Console.WriteLine("Group : {0}", item.Address)
            For Each detailItem In item.Persons
                Console.WriteLine("{0}", detailItem.Name)
            Next
        Next
        Console.ReadLine()
    End Sub
此例执行结果如图1。
[图1]
Group : Taipai
code6421
Group : USA
jeffray
catch
Group : NY
joe
此处有两个尚未提及的指令,Into是将Group后的结果放到g变数中,于VB.NET中,你必须指定g的值为Group关键字,此处Group关键字的内涵值为Group后的结果,是一个IEnumerable(Of Object)的物件。Select New With则是应用了VB.NET 2008的Anonymous Type技巧,建立一个简单的物件,其中含有Address及Persons两个属性,Address的值来自于Address,也就是Group o By o.Address的键值,那就是Address的值。使用Select New With时,记得属性名称需以【.】作为前导字,如下:
Select New With {.Name = "code6421", .Age = 18, .Address = "Taipai"},
New With除了可放在Select之后,也可直接用来建立Anonymous Type,如下所示:
New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}
而g本身是一个分类后的IEnumerable(Of Object)类型物件,其内所含的就是以Address值所分类后的Persons物件。与where-expression一样,group-expression中也可以包含query-expression,如程式4。
[程式4]
Sub TestComplexGroupBy2()
 
        Dim p2() = { _
                   New With {.Name = "code6421", .Hand = "Manager", .Address = "Taipai"}, _
                   New With {.Name = "tom", .Hand = "Director", .Address = "Taipai"}, _
                   New With {.Name = "jeffray", .Hand = "Programmer", .Address = "NT"} _
                   }
 
        Dim result = From s In p2 Group s By Address = (From s1 In p2 Where _
            s.Name = s1.Name Select s1.Address).ToArray()(0) Into g = Group _
                     Select New With {.Address = Address, .Persons = g}
        For Each item In result
            Console.WriteLine("Group : {0}", item.Address)
            For Each detailItem In item.Persons
                Console.WriteLine("{0}", detailItem.Name)
            Next
        Next
        Console.ReadLine()
    End Sub
执行结果如图2。
[图2]
Group : Taipai
code6421
tom
Group : NT
jeffray
 
Join-expression
 
 LINQ Expression也支援SQL中常用的Join指令,如程式5。
[程式5]
Sub TestJoin()
        Dim p1() = { _
                     New With {.Name = "code6421", .Address = "Taipai"}, _
                     New With {.Name = "tom", .Address = "Taipai"}, _
                     New With {.Name = "jeffray", .Address = "NY"} _
                   }
        Dim p2() = { _
                     New With {.Name = "code6421", .Title = "Manager"}, _
                     New With {.Name = "tom", .Title = "Director"}, _
                     New With {.Name = "jeffray", .Title = "Programmer"} _
                   }
        Dim result = From s In p1 _
                     Join s1 In p2 On s.Name Equals s1.Name _
                     Select New With {.Name = s.Name, .Address = s.Address, .Title = s1.Title}
        For Each item In result
            Console.WriteLine("Name : {0}, Address : {1}, Title : {2}", _
                   item.Name, item.Address, item.Title)
        Next
        Console.ReadLine()
    End Sub
图3是执行结果。
[图3]
Name : code6421, Address : Taipai, Title : Manager
Name : tom, Address : Taipai, Title : Director
Name : jeffray, Address : NY, Title : Programmer
程式6是运用一个以上join的例子。
[程式6]
Sub TestJoin2()
        Dim p1() = { _
                     New With {.Name = "code6421", .Address = "Taipai"}, _
                     New With {.Name = "tom", .Address = "Taipai"}, _
                     New With {.Name = "jeffray", .Address = "NY"} _
                   }
        Dim p2() = { _
                     New With {.Name = "code6421", .Title = "Manager"}, _
                     New With {.Name = "tom", .Title = "Director"}, _
                     New With {.Name = "jeffray", .Title = "Programmer"} _
                   }
        Dim p3() = { _
                     New With {.Name = "code6421", .Hand = "Left"}, _
                     New With {.Name = "tom", .Hand = "Left"}, _
                     New With {.Name = "jeffray", .Hand = "Right"} _
                   }
        Dim result = From s In p1 _
                     Join s1 In p2 On s.Name Equals s1.Name _
                     Join s2 In p3 On s.Name Equals s2.Name _
                     Select New With {.Name = s.Name, .Address = s.Address, _
                          .Title = s1.Title, .Hand = s2.Hand}
        For Each item In result
            Console.WriteLine("Name : {0}, Address : {1}, Title : {2}, Hand : {3}", _
               item.Name, item.Address, item.Title, item.Hand)
        Next
        Console.ReadLine()
    End Sub
执行结果如图4。
[图4]
Name : code6421, Address : Taipai, Title : Manager, Hand : Left
Name : tom, Address : Taipai, Title : Director, Hand : Left
Name : jeffray, Address : NY, Title : Programmer, Hand : Right
 
Order-by-expression
 
 Order-by-expression用于将query-expression的结果集排序,如程式7。
[程式7]
Sub TestOrderLinq()
        Dim p() = {"1111", "3333", "2222"}
        Dim result = From s1 In p Order By s1 Descending Select s1
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
图5是执行结果。
[图5]
3333
2222
1111
Descending指的是倒序,Ascending则是正序,未加上Descending也未加上Ascending,则预设为Ascending,Order By可以有多个,彼此以 , 分接,每个都能有正倒序之修饰字,如下所示。
Sub TestOrderLinq2()
        Dim p1() = { _
                     New With {.Name = "code6421", .Address = "Taipai"}, _
                     New With {.Name = "tom", .Address = "Taipai"}, _
                     New With {.Name = "jeffray", .Address = "NY"} _
                     }
        Dim result = From s1 In p1 Order By s1.Name, s1.Address Descending Select s1
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
 
 
Select-expresssion
 
 Select-expression出现于query-expression的尾端,涵意是由结果集中取出资料,其中较特别的是,有时我们会使用Select New With语句由结果集中取出需要的栏位,建构出另一个型态的回传物件,如同于join范例中的片段。
Dim result = From s In p1 _
                     Join s1 In p2 On s.Name Equals s1.Name _
                     Join s2 In p3 On s.Name Equals s2.Name _
                     Select New With _
                   {.Name = s.Name, .Address = s.Address, .Title = s1.Title, .Hand = s2.Hand}
这是运用了VB.NET 2008的Anonymouse Type功能所达到的。
 
Distinct
 
 如同SQL中的Distinct一样,LINQ To Object Framework中的Distinct函式允许设计师将一阵列中特定栏位值相同的部份,仅撷取出一代表值,见程式8。
[程式8]
Sub TestSelectDistinct()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = (From s1 In persons Select s1.Address).Distinct()
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例的执行结果如图6。
[图6]
Taipai
USA
NY
此例仅列出一个Taipai,这就是Distinct函式的能力,相同值者仅取一代表值。
 
Select Many 功能
 
 截至目前为止的例子皆以一个query-expression作为开始,事实上LINQ Expression支援query-expression的串接,名为Select Many功能,见程式9。
[程式9]
Sub TestSelectMany()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim p2() = { _
                     New With {.Title = "Director"}, _
                     New With {.Title = "Programmer"}, _
                     New With {.Title = "Manager"} _
                    }
 
        Dim result = From s1 In persons _
                     From s2 In p2 _
                     Select New With {.Name = s1.Name, .Title = s2.Title}
 
        For Each item In result
            Console.WriteLine("Name : {0}, Title : {1}", item.Name, item.Title)
        Next
        Console.ReadLine()
    End Sub
此例的执行结果如图7。
[图7]
Name : code6421, Title : Director
Name : code6421, Title : Programmer
Name : code6421, Title : Manager
Name : jeffray, Title : Director
Name : jeffray, Title : Programmer
Name : jeffray, Title : Manager
Name : catch, Title : Director
Name : catch, Title : Programmer
Name : catch, Title : Manager
Name : joe, Title : Director
Name : joe, Title : Programmer
Name : joe, Title : Manager
 
Index
 
 LINQ To Object Framework的LINQ Expression允许指定Index,什么是Index呢 ?请看程式10。
[程式10]
Sub TestIndex()
        Dim persons() = { _
                        New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = From s1 In persons Select s1.Name(0)
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例会列出阵列中Name属性值的第一个字元,事实上这是当然的结果,因为where后面的语句,会被VB.NET编译器当成Lambda Expression处理,而Name属性是string型态,自然也能对其使用()阵列存取子了,同时也因为是string型别,而且会被转为Lambda Expression,自然能呼叫属于string型别的Contains函式了。
 
Take、TakeWhile
 
 Take函式可以让设计师由一个IEnumerable<T>物件中取出指定的元素数量,请见程式11。
[程式11]
Sub TestTake()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = persons.Take(2)
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
此例会从p1中取出前两个元素,也就是code6421及jeffray,Take函式的功能有点像是SQL Server的Select TOP。另一个TakeWhile函式则可以让设计师以while的方式来取出IEnumerable<T>中的元素,请见程式12。
[程式12]
Sub TestTakeWhile()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = persons.TakeWhile(Function(p) p.Address = "Taipai")
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
此例的执行结果与Take相同,不同之处在于TakeWhile会一直取出元素,直到某个元素不符合所指定的Lambda Expression为止,这意味著假如指定Address == “USA”时,将不会有任何元素列出,因为阵列中的第一个元素就已经不符合条件了,所以While动作就结束了。
 
Skip、SkipWhile
 
 相对于Take,Skip允许设计师略过指定的元素数,如程式13。
[程式13]
Sub TestSkip()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = (From s1 In persons Select s1).Skip(2)
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
此例会由catch的元素开始列出。另一个SkipWhile函式与TakeWhile的概念相同,SkipWhile也是以While的观念执行Skip动作,如程式14。
[程式14]
Sub TestSkipWhile()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = (From s1 In persons Select s1).SkipWhile(Function(p) p.Address = "Taipai")
        For Each item In result
            Console.WriteLine(item.Name)
        Next
        Console.ReadLine()
    End Sub
与TakeWhile一样,若指定Address == “USA”时,那么将会列出所有元素,因为在第一个元素时,While就已经结束了。
 
First、FirstOrDefault
 
   First函式允许设计师指定一个Lambda Expression条件式, 她将以此对IEnumerable(Of T)中的元素查询,并传回第一个符合条件的元素,当不指定条件式时,First会传回第一个元素。与Where不同,First只会传回符合资料的第一个元素,而非内含所有符合条件元素的IEnumerable(Of T)结果集,见程式15。
[程式15]
Sub TestFirst()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = (From s1 In persons Select s1).First()
        Console.WriteLine(result.Name)
        Console.ReadLine()
    End Sub
下例则是使用条件式的写法。
Sub TestFirst2()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = (From s1 In persons Select s1).First(Function(p) p.Address = "USA")
        Console.WriteLine(result.Name)
        Console.ReadLine()
    End Sub
当IEnumerable(Of T)中未含有符合条件的元素时,First将会引发例外。另一个FirstOrDefault函式与First函式类似,唯一不同之处是当IEnumerable(Of T)中未含有符合条件之元素时,她将回传元素之型别的预设值(object为null、数值为0)。与First相对的是Last函式,可取得最后一个元素,她也有另一个同型函式为LastOrDefault,设计理念与FirstOrDefault相同。
 
ElementAt、ElementAtOrDefault
 
 ElementAt函式可以取得一个物件集中特定位置的元素,如下所示:
Sub TestElementAt()
        Dim p1() As String = {"code6421", "tom", "cathy"}
        Dim result = p1.ElementAt(1)
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
此例会列出tom这个元素,当指定位置超出物件集大小时,ElementAt会抛出例外。如同FirstOrDefault一般,ElementAt有另一个同型函式:ElementAtOrDefault,当使用此函式时指定位置超出物件集大小时,不会触发例外,而是直接回传预设值(object为null,数值为0)。
 
ToArray、ToList、ToDictionary
 
 这三个函式会将IEnuermable(Of T)转成Array、List或是Dictionary型别,预设情况下,当我们对某个IEnumerable(Of T)下达Where等条件式时,所取得的结果会是一个IEnumerable(Of T)物件,此时所有条件都尚未执行比对的动作,当对这个IEnumerable(Of T)物件下达MoveNext(For Each会触发此函式)时,该物件才会进行条件比对。ToArray等函式可以改变此行为模式,当我们对IEnumerable(Of T)物件呼叫这些函式时,其将会以For Each一一巡览IEnumerable(Of T)物件中的元素并进行条件比对,然后放到另一个结果值后传回,这也就是说,呼叫此函式所传回的结果值,将会是已经完成比对后的结果值,操作这个结果集自然比直接操控具条件的结果集来得有效率,程式16是一个使用ToDictionary函式的例子。
[程式16]
Sub TestToDictionary()
        Dim persons() = { _
                        New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                        New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                        New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                        New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                        }
        Dim result = persons.ToDictionary(Function(p) p.Name)
        Console.WriteLine(result("code6421").Address)
        Console.ReadLine()
    End Sub
ToDictionary函式为以指定的键值为主键,将元素转为Dictionary物件,此时你可透过预设存取子,以主键为参数来取得对应之元素。
 
Union
 
   Union函式可以将两个IEnumerable(Of T)物件整合成一个, 如程式17。
[程式17]
Sub TestUnion()
        Dim p1() As Integer = {1, 3, 5, 7, 9, 11}
        Dim p2() As Integer = {2, 4, 6, 8, 10, 11}
        Dim result = p1.Union(p2)
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
执行结果图10。
[图10]
1
3
5
7
9
11
2
4
6
8
10
如结果所示,两个IEnumerable(Of T)物件被整合成一个了,在呼叫Union函式时,我们也可以指定一个实作了IEqualComparaer介面的物件来改变结果,如程式18所示。
[程式18]
Sub TestUnion2()
        Dim p1() As String = {"code6421", "tom", "cathy"}
        Dim p2() As String = {"code6421", "Tom", "cathy"}
        Dim result = p1.Union(p2, _
            StringComparer.Create(System.Globalization.CultureInfo.CurrentCulture, True))
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例的意思是,以无关大小写的字串比对方式,比对两个IEnumerable(Of T)物件中的元素,避免列出重复的元素,图11是此例的执行结果。
[图11]
code6421
tom
cathy
 
Intersect
 
 Intersect函式可以找出两个IEnumerable(Of T)物件中相同的元素,如程式19。
[程式19]
Sub TestIntersect()
        Dim p1() As Integer = {1, 3, 5, 7, 9, 11}
        Dim p2() As Integer = {2, 4, 6, 7, 10, 11}
        Dim result = p1.Intersect(p2)
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例的执行结果仅会列出相同的数字,也就是7与11。在呼叫Intersect函式时,也可以像Union函式般,指定一个 实作了IEqualComparer介面的物件做为比对基准,如程式20。
[程式20]
Sub TestIntersect2()
        Dim p1() As String = {"code6421", "tom", "cathy"}
        Dim p2() As String = {"code6421", "Tom", "cathy"}
        Dim result = p1.Intersect(p2, _
          StringComparer.Create(System.Globalization.CultureInfo.CurrentCulture, True))
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例是以无关大小写字串的方式比对,执行结果图12。
[图12]
code6421
tom
cathy
Tom、tom因字串是以不分大小写比对的方式,所以仅列出一个。
 
Except
 
 Except函式允许传入一个IEnumerable(Of T)物件,以其来比对原始的IEnumerable(Of T)物件中的元素,仅列出未出现在所传入IEnumerable(Of T)物件中的元素,见程式21。
[程式21]
Sub TestExcept()
        Dim p1() As Integer = {1, 3, 5, 7, 9, 11}
        Dim p2() As Integer = {2, 4, 6, 8, 10, 11}
        Dim result = p1.Except(p2)
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
End Sub
执行结果如图13。
[图13]
1
3
5
7
9
如结果所示,11已经被排除了。与Union函式相同,Except函式也允许传入一个实作了IEqualComparer介面的物件,用来当做比对的基准,如程式22。
[程式22]
Sub TestExcept2()
        Dim p1() As String = {"code6421", "tom", "cathy2"}
        Dim p2() As String = {"code6421", "Tom", "cathy"}
        Dim result = p1.Except(p2, _
         StringComparer.Create(System.Globalization.CultureInfo.CurrentCulture, True))
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
    End Sub
此例仅会列出cathy2这个元素。
 
OfType、Cast
 
 OfType、Cast是LINQ To Object Framework所提供的转型函式,可以将一个IEnumerable(Of T)物件中的元素转型为所要求的型别,两者不同之处在于OfType函式于转型失败时并不会丢出例外,而Cast则会丢出例外,见程式23。
[程式23]
Sub TestOfType()
        Dim ar As New ArrayList()
        ar.Add("Test1")
        ar.Add("Test2")
        ar.Add("Test3")
        ar.Add("Test4")
        ar.Add(16)
        Dim result = ar.OfType(Of String)()
        'Of Type will not raise invalid type-casting exception.
        'if you need receive the exception at type-casting,use Cast.
        For Each item In result
            Console.WriteLine("Type is {0} and value is {1}", item.GetType(), item)
        Next
        Console.ReadLine()
    End Sub
此例执行结果如图14。
[图14]
Type is System.String and value is Test1
Type is System.String and value is Test2
Type is System.String and value is Test3
Type is System.String and value is Test4
 
Sum、Average、Min、Max、Count、
 
 LINQ To Object Framework也提供了Sum、Min、Max、Average、Count等之类常见于SQL指令中的功能函式,Sum用于加总、Min用于取出最小值、Max则用于取出最大值、Count则用于计算符合条件的元素数目,基本上这四个函式的用法大致相同,因此此处我只以Sum函式做为代表例,见程式24。
[程式24]
Sub TestSum()
        Dim list() As Integer = {18, 20, 25}
        Dim result = list.Sum()
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
无意外,此例的执行结果为63,无参数的Sum函式仅作用于数值型别,如Int32、Int64、Decimal、Double、Single等等,当需加总的是一个物件时,就必需传入一个Lambda Expression来协助Sum函式运算,见程式25。
[程式25]
Sub TestSum2()
        Dim persons() = { _
                         New With {.Name = "code6421", .Age = 18, .Address = "Taipai"}, _
                         New With {.Name = "jeffray", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "catch", .Age = 18, .Address = "USA"}, _
                         New With {.Name = "joe", .Age = 18, .Address = "NY"} _
                         }
        Dim result = persons.Sum(Function(p) p.Age)
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
 
Aggregate
 
 Sum函式用于计算加总值,但若需要的不仅仅是加法呢?例如乘法、减法、除法等等,此时另一个Aggregate函式就能派上用场了,见程式26。
[程式26]
Sub TestAggreate()
       Dim list() As Integer = {18, 20, 25}
        Dim result = list.Aggregate(Function(x, y) x * y)
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
此例的结果是9000,也就是18*20*25后的结果,另外于呼叫Aggregate函式时,也可以指定一个起始值,如程式27。
[程式27]
Sub TestAggreate2()
        Dim list() As Integer = {18, 20, 25}
        Dim result = list.Aggregate(9000, Function(x, y) x * y)
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
此例的结果是,9000*18*20*25 = 81000000。
 
语法之外,明确使用LINQ TO Object Framework的理由
 
 在本文中的范例,偶而会使用Where函式而不是使用LINQ Expression所提供的where
,读者或许对此举会有所疑惑,既然已经有了对应的LINQ Expression,又何必使用函式呼叫呢?简单的说,LINQ Expression的转换支援并无法完全取代函式呼叫,以Where例子来说,当使用函式呼叫时,我们可以写下很复杂的处理函式,如程式28。
[程式28]
Sub TestWhereFunction()
        Dim list() As String = {"code6421", "tom", "cathy"}
        Dim result = list.Where(AddressOf CompareData)
        For Each item In result
            Console.WriteLine(item)
        Next
        Console.ReadLine()
End Sub
 
Function CompareData(ByVal val As String) As Boolean
        Dim conn As New SqlClient.SqlConnection()
        'do something of you want..............
        Return True
End Function
这是无法以单纯的LINQ Expression来办到的。
注:C#的Lambda Expression可以允许写得很复杂,如下:
var p5 = p1.Where(x =>
                              {
                                  bool result = false;
                                  SqlConnection conn = new SqlConnection("....");
                                  conn.Open();
                                  return result;
                              });
不过VB.NET 2008并不支援此类写法,所以还是得走Function模式。
 
 
Dim、Dim xxx() As Type的差异
 
 一般来说,你可以以Dim来宣告一个阵列,而不事先指定其型别,如下所示:
Dim list() = {1, 2, 3}
此时当你透过list来呼叫某些LINQ函式时,会发生要求传入一个Lambda Expression的情况,例如Sum就得写成下面这样:
Dim list() = {1, 2, 3}
Dim result = list.Sum(Function(p) p)
这是因为list阵列被当成Object阵列的缘故,直接于Dim时给予型别,那么就可以用下面的方式呼叫Sum函式。
Sub Main()
        Dim list() As Integer = {1, 2, 3}
        Dim result = list.Sum()
        Console.WriteLine(result)
        Console.ReadLine()
End Sub
所以,如非必要,使用Dim As Type的方式,可以避免因编译器的猜测所产生的不必要问题。
 
 
还是效能的课题
 
 在LINQ To Object Framework中,效能并非是首要考量的课题,因此许多功能都隐涵效能的问题,例如where,预设是遍览IEnumerable(Of T)物件中的元素,并一一以传入的Lambda Expression来验证,这在某些情况下是不适用的,因为 有时我们只是想寻找某一个符合条件的元素,而且该IEnumerable(Of T)物件是拥有既定排序的,此时采取Binary Search的演算法来搜寻所要的资料是较有效率的。因此!设计师应该将LINQ To Object Framework视为是一个查询演算法的Framework,但其不保证以最快的方式来完成所要求的动作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章