對於複雜的對象列表,運行時引擎如何才能經過比較肯定兩個對象是否重複?對於複雜對象,必須提供一個比較器,即實現IEqualityComparer(Of T)執行比較的一個類實例。數據結構
假設有一個包括客戶信息的序列,你但願獲得這些客戶坐在國家的專門列表。若是已有一個簡單的國家列表,可以使用默認比較器來比較字符串。但有可能面臨的是一個客戶類表(你固然可使用select方法投影列表以僅擁有國家列表,但這樣你會丟失全部其他數據,形成使用列表的其餘計算失效)。dom
先看下IEqualityComparer的實現函數
Public Class CustomerCountryComparer Implements IEqualityComparer(Of Customer) Public Function Equals1(x As Customer, y As Customer) As Boolean Implements IEqualityComparer(Of Customer).Equals Return x.Country.Equals(y.Country) End Function Public Function GetHashCode1(obj As Customer) As Integer Implements IEqualityComparer(Of Customer).GetHashCode Return obj.Country.GetHashCode() End Function End Class
再看咱們兩個不一樣的實現,兩個輸出的結果並不同,第一個會按照Country、ContactName進行distinct編碼
Dim db As New SimpleDataContext Dim customersOFcountries = db.Customers _ .Select(Function(customer) New With {customer.Country, customer.ContactName}) _ .Distinct() Console.WriteLine("Country Info:") For Each country In customersOFcountries Console.WriteLine(" " & country.ContactName & " live in " & country.Country) Next Console.WriteLine() Dim customersOFcountries1 = db.Customers.Distinct(New CustomerCountryComparer) _ .Select(Function(customer) New With {customer.Country, customer.CustomerId, customer.ContactName}) For Each country In customersOFcountries1 Console.WriteLine(country.CustomerId & "." & country.ContactName & " live in " & country.Country) Next
在第一個實現中,distinct會以country、contact爲key,即便用默認的比較器,進行比較;而在第二個實現中,則spa
採用指定的比較器進行比較,能夠看到會先按照比較器,組成一個county惟一的列表,再將其中的信息進行輸出,而不是按照輸出信息進行distinct操做。.net
在實際工做中,咱們極可能須要妥善的處理空序列,此時咱們須要Enumerable.DefaultIfEmpty方法,當咱們選擇序列爲空時,則返回默認值的實例。code
Dim noCustomers = db.Customers.Where( _ Function(customer) customer.Country = "XXX") Dim noCustomers1 = noCustomers.DefaultIfEmpty( _ New Customer With {.CustomerId = "XXXXXXXXX"}) Dim results = String.Format( _ "noCustomers contains {0} element(s) . " & _ "noCustomers1 contains {1} elemetn(s). ", _ noCustomers.Count(), noCustomers1.Count()) Console.WriteLine(results) For Each customer As Customer In noCustomers1 Console.WriteLine(customer.CustomerId) Next
在上面代碼中,使用了空列表來填充noCustomers變量(沒有國家爲XXX的客戶),而後使用DefaultIfEmpty方法建立了noCustomers1,併爲其提供了默認值。orm
可使用Enumerable.OrderBy,Enumerable.ThenBy,Enumerable.OrderByDescending和Enumerable.ThenByDescending先提供初始排序,而後提供一個或多個次要排序(使用升序或降序排序)。對象
Dim customers = db.Customers.Where( _ Function(customer) customer.ContactTitle = "Owner") Dim results = customers _ .OrderBy(Function(customer) customer.Country) _ .ThenByDescending(Function(customer) customer.City) _ .ThenBy(Function(customer) customer.ContactName) _ .Select(Function(customer) String.Format("({0},{1}) {2}", _ customer.Country, customer.City, customer.ContactName)) For Each result As String In results Console.WriteLine(result) Next
按照上述代碼,輸出的結果爲,將contactTitle爲owner的客戶,首先按照國家排序,而後按照城市降序排序,最後按照每一個城市中的聯繫人姓名進行排序。排序
一些應該程序要求您確認序列是否知足某些特定的條件。是否有任何元素知足某個條件嗎?是全部元素都知足個條件嗎?序列中是否出現了某個特定的項目?兩個序列是否相等?Enumerable類規定了用於提供全部此類信息的方法。
要肯定序列是否包含某個元素,請調用不包含任何參數的Enumerable.Any方法。
要確認序列中是否包含知足某個條件的的元素,請使用此方法,將這個特定條件經過lambda表達式傳遞給Enumerable.Any方法。若是輸入序列中存在元素或者存在匹配提供條件的元素,則Enumerable.Any方法放回true
Dim results = db.Products _ .Where(Function(product) product.CategoryId = 1) 'determine if a list has any elements: Dim anyElements = results.Any() 'determine list match extension method Dim matchingElements = results _ .Any(Function(product) product.ProductName.StartsWith("M")) Console.WriteLine("list has any elements ? {0}", anyElements) Console.WriteLine("list has elements matching the method ? {0}", _ matchingElements)
要肯定序列的全部成員是否均知足某個條件,請提供一個指定條件的方法,並調用Enumerable.All方法。
Dim results = db.Products _ .Where(Function(product) product.CategoryId = 1) Dim allElements = results _ .All(Function(product) product.UnitsInStock > 5) Console.WriteLine("all elements match the method? {0}", allElements)
要肯定序列是否包含某個特定元素,請調用Enumerable.contains.若是要搜索一個簡單的值(如字符串或整數),可以使用默認比較器,而且僅需提供搜索的值便可。若是嘗試肯定序列中是否存在更爲複雜的對象,必須提供IEqualityComparer(Of T)的實例。
Dim numbers = Enumerable.Range(1, 10) Dim contains5 = numbers.Contains(5) Dim db As New SimpleDataContext Dim results = db.Customers _ .Where(Function(customer) customer.ContactTitle = "Owner") Dim item As New Customer _ With {.ContactName = "Bob", .Country = "USA"} Dim containsUsa = results.Contains(item, New CustomerCountryComparer) Console.WriteLine("numbers has 5? {0}", contains5) Console.WriteLine("containsUsa ? {0}", containsUsa)
要肯定兩個序列是否相等,請調用Enumerable.SequenceEqual方法,與Enumerable.Contains方法同樣,可以使用默認比較器比較包含簡單值的兩個序列,也可使用自定義比較器比較包含更復雜對象的兩個序列。
若是兩個序列並不包含相同的類型或長度不一樣,比較當即失敗。若是它們包含相同的類型且長度相同,Enumerable.SquenceEqual方法將使用指定的比較器比較每一個item。
Const count As Integer = 10 Dim rnd As New Random Dim start = rnd.Next Dim s1 = Enumerable.Range(start, count) start = rnd.Next Dim s2 = Enumerable.Range(start, count) Dim sequencesEqual = s1.SequenceEqual(s2) Console.WriteLine("SequencesEqual = {0}", sequencesEqual) Dim db As New SimpleDataContext Dim customers1 = db.Customers _ .Where(Function(customer) customer.ContactTitle = "Owner") Dim customers2 = db.Customers _ .Where(Function(customer) customer.ContactTitle = "Accounting Manager") sequencesEqual = _ customers1.SequenceEqual(customers2, New CustomerCountryComparer) Console.WriteLine("SequencesEqual = {0}", sequencesEqual)
若是你須要獲取可枚舉序列的處理結果,並將其傳遞給某個要求特定類型的方法,或者您須要使用存儲在可枚舉序列中的數據來調用某一特定類型的方法,可能須要調用其參數須要一個Enumerable類型變量方法,以將該數據轉換爲其餘類型。
咱們拿String.Join爲例,可能不太合適,該方法在.net 3.5中的簽名爲Join(String,string())。
假設咱們須要結果爲篩選後結果的字符串,並用「,」間隔。
Dim db As New SimpleDataContext Dim customers = db.Customers _ .Where(Function(customer) customer.Country = "France") _ .Select(Function(customer) customer.ContactName) Dim nameList = String.Join(",", customers.ToArray())
實際上在.net 4.5中,String.Join()已經有了Join(string,IEnumerable(Of string))的重載。
儘管能夠從可枚舉序列轉換爲通用的Dictonary,但必須至少提供一個函數來指示生成鍵值的方式。Enumerable.ToDictionary方式提供了多個重載,容許你指定鍵選擇器、值選擇器、鍵比較器以及值比較器方法的各類組合。
Dim db As New SimpleDataContext Dim someProducts = db.Products _ .Where(Function(product) product.CategoryId = 1) Dim productsDictionary = _ someProducts.ToDictionary(Function(product) product.ProductId) 'display the contents of the dictionary : For Each keyValuePair As KeyValuePair(Of Integer,Product) In productsDictionary Console.WriteLine("{0},{1}", _ keyValuePair.Key, keyValuePair.Value.ProductName) Next
上述代碼,使用productID字段做爲鍵值來獲取someProducts變量的內容,並將其轉換爲Dictionary.而後將代碼將遍歷字典中的全部項目並打印鍵和每一個字典項目的值對應的一個字段。
若是你但願使用一個並不是簡單類型的值(如字典中的鍵),該怎麼辦?或許你但願使用整個Product做爲字典鍵。若是是這樣,必須再次提供一個自定義比較器的實例,以給出一種比較各個鍵實例的方式。
Public Class ProductComparer Implements IEqualityComparer(Of Product) Public Function Equals1(x As Product, y As Product) As Boolean _ Implements IEqualityComparer(Of Product).Equals Return x.ProductId.Equals(y.ProductId) End Function Public Function GetHashCode1(obj As Product) As Integer _ Implements IEqualityComparer(Of Product).GetHashCode Return obj.GetHashCode() End Function End Class
Dim productsDictionary1 As Dictionary(Of Product, String) = _ someProducts.ToDictionary(Function(pro) pro, _ Function(pro) pro.ProductName, New ProductComparer) For Each keyValuePair As KeyValuePair(Of Product,String) In productsDictionary1 Console.WriteLine("{0},{1}", keyValuePair.Key.ProductId, keyValuePair.Value) Next
上述代碼,使用整個Product做爲字典中每一個項目的鍵值;只須要提供一個自定義類來實現 IEqualityComparer(Of Product),此類將以productID爲標準,來判斷Product是否相等。
一些方法明確要求以通用列表List做爲輸入,而不是可枚舉序列。要準換爲List,請使用Enumerable.ToList方法。
Dim productNames = db.Products _ .Select(Function(product) product.ProductName) _ .ToList() Dim results = String.Format("Chang was found at index {0}", _ productNames.IndexOf("Chang"))
Dictionary數據結構將鍵映射爲單個值。Lookup數據結構將鍵映射爲一組值。此結構是分層Enumerable實例的最佳配項(例如,連接到許多Product實例的CategoryID)。Enumerable.ToLookup方法會爲你執行轉換(假設你有一個簡單的一鍵對多值的層次結構)。
Dim db As New SimpleDataContext Dim products = db.Products _ .Where(Function(product) product.UnitPrice > 40) _ .OrderBy(Function(product) product.CategoryId) Dim lookup = products _ .ToLookup(Function(product) product.CategoryId, _ Function(product) product) Dim sw = New StringWriter For Each grouping As IGrouping(Of Integer?,Product) In lookup sw.WriteLine("Category ID = {0}", grouping.Key) For Each product As Product In grouping sw.WriteLine(" {0} ({1:C})", _ product.ProductName, product.UnitPrice) Next Next Console.WriteLine(sw.ToString())
上述代碼,將IEnumerable(Of Product)序列轉換爲Lookup,其中每一個鍵都是CategoryID,對應的值能夠理解爲
Product的集合,此時categoryID和product是一對多的關係。ToLookup方法的第一個參數是用來肯定鍵的函數,第二個函數用來肯定各項目值的函數。
在使用LINQ查詢非泛型IEnumerable集合(ArrayList)時,你必須顯式聲明範圍變量的類型以反映此集合中對象的特定類型。
Dim que = From item As String In items
經過指定範圍變量的類型,你將ArrayList中的各項強制轉換爲string。
在查詢表達式中,調用Cast(OF TResult)方法與使用顯式類型化的範圍變量等效。注意若是沒法執行指定的強制轉換,則Cast(of TResult)引起異常。出現這種狀況,你能夠在轉換以前使用OfType方法對列表進行篩選。 Cast(of TResult)和OfType(Of TResult)是兩種對非泛型IEnumerable類型操做的標準查詢運算符方法。
Dim items As New ArrayList items.Add("January") items.Add(1) items.Add("August") items.Add(14) items.Add("October") items.Add("April") items.Add(38) Dim stringItem = items.OfType(Of String)() Dim query = stringItem.Cast(Of String)() Dim results = query.Where(Function(item) item.StartsWith("A")) For Each result As String In results Console.WriteLine(result) Next
上述代碼,將篩選ArrayList數據,以僅檢索以字母「A」開頭的項目。
最後,Enumerable.AsEnumerable方法容許你將源類型視爲IEnumerable,這樣你即可以使用IEnumerable的方法,而不是已實現類中的方法。這些方法在一些特定狀況下很是有用,可是在常規編碼中不大可能用到它。
假設在LINQ使用延遲執行來檢索數據,而你但願虛擬化對大型數據集的訪問,這時你須要採用某種方式從數據源檢索特定的行號(從數據內的特定偏移開始)。要知足這些需求,可使用Enumerable.Take和Enumerable.Skip方法。這些方法容許你在開始獲取行以前就指定要獲取的行號和要跳過的行號。
Dim db As New SimpleDataContext Dim products = db.Products _ .OrderBy(Function(product) product.ProductName) _ .Select(Function(product) String.Format("{0}:{1}", _ product.ProductId, product.ProductName)) _ .Skip(10).Take(5)
上述代碼,將跳過10行後返回5行;
你可使用Enumerable.TakeWhile和Enumerable.SkpWhile方法,經過設置條件獲取和跳過序列中的值。當條件爲真時,TakeWhile方法會獲取值並返回一個序列,其中將包括獲取的全部值。只要條件爲真,SkipWhile方法會跳過值並返回輸入序列的其他部分。