LINQ(Language Integrated Query,語言集成查詢)提供了類似于SQL的語法,能對集合進行遍歷、篩選和投影。一旦掌握了LINQ,你就會發(fā)現(xiàn)在開發(fā)中再也離不開它。 前言 C#中的集合表現(xiàn)為數(shù)組和若干集合類。不管是數(shù)組還是集合類,它們都有各自的優(yōu)缺點。如何使用好集合是我們在開發(fā)過程中必須掌握的技巧。 不要小看這些技巧,一旦在開發(fā)中使用了錯誤的集合或針對集合的方法,應用程序將會背離你的預想而運行。 1、元素數(shù)量可變的情況下不應使用數(shù)組 在C#中,數(shù)組一旦被創(chuàng)建,長度就不能改變。如果我們需要一個動態(tài)且可變長度的集合,就應該使用ArrayList或List<T>來創(chuàng)建。而數(shù)組本身,尤其是一維數(shù)組,在遇到要求高效率的算法時,則會專門被優(yōu)化以提升其效率。 一維數(shù)組也稱為向量,其性能是最佳的,在IL中使用了專門的指令來處理它們(如newarr、ldelem、ldelema、ldlen和stelem)。 從內存使用的角度來講,數(shù)組在創(chuàng)建時被分配了一段固定長度的內存。如果數(shù)組的元素是值類型,則每個元素的長度等于相應的值類型的長度;如果數(shù)組的元素是引用類型,則每個元素的長度為該引用類型的IntPtr.Size。 數(shù)組的存儲結構一旦被分配,就不能再變化。而ArrayList是數(shù)組結構,可以動態(tài)地增減內存空間,如果ArrayList存儲的是值類型,則會為每個元素增加12字節(jié)的空間,其中4字節(jié)用于對象引用,8字節(jié)是元素裝箱時引入的對象頭。 List<T>是ArrayList的泛型實現(xiàn),它省去了拆箱和裝箱帶來的開銷。 注意 由于數(shù)組本身在內存上的特點,因此在使用數(shù)組的過程中還應該注意大對象的問題。所謂“大對象”,是指那些占用內存超過85 000字節(jié)的對象,它們被分配在大對象堆里。大對象的分配和回收與小對象相比,都不太一樣,尤其是回收,大對象在回收過程中會帶來效率很低的問題。所以,不能肆意對數(shù)組指定過大的長度,這會讓數(shù)組成為一個大對象。
2、多數(shù)情況下使用foreach進行循環(huán)遍歷 采用foreach最大限度地簡化了代碼。 它用于遍歷一個繼承了IEmuerable或IEmuerable<T>接口的集合元素。借助于IL代碼可以看到foreach還是本質就是利用了迭代器來進行集合遍歷。如下: List<object>list=new List<object>(); 除了代碼簡潔之外,foreach還有兩個優(yōu)勢
3、foreach不能代替for foreach存在的一個問題是:它不支持循環(huán)時對集合進行增刪操作。取而代之的方法是使用for循環(huán)。 不支持原因:
無論是for循環(huán)還是foreach循環(huán),內部都是對該數(shù)組的訪問,而迭代器僅僅是多進行了一次版本檢測。事實上,在循環(huán)內部,兩者生成的IL代碼也是差不多的。 4、使用更有效的對象和集合初始化 舉例: class Program { 對象初始化設定項支持在大括號中對自動實現(xiàn)的屬性進行賦值。以往只能依靠構造方法傳值進去,或者在對象構造完畢后對屬性進行賦值。現(xiàn)在這些步驟簡化了,初始化設定項實際相當于編譯器在對象生成后對屬性進行了賦值。 集合初始化也同樣進行了簡化:
重點:初始化設定項絕不僅僅是為了對象和集合初始化的方便,它更重要的作用是為LINQ查詢中的匿名類型進行屬性的初始化。由于LINQ查詢返回的集合中匿名類型的屬性都是只讀的,如果需要為匿名類型屬性賦值,或者增加屬性,只能通過初始化設定項來進行。初始化設定項還能為屬性使用表達式。 舉例 List<Person>personList2=new List<Person>() 5、使用泛型集合代替非泛型集合 注意,非泛型集合在System.Collections命名空間下,對應的泛型集合則在System.Collections.Generic命名空間下。 泛型的好處不言而喻,,如果對大型集合進行循環(huán)訪問、轉型或拆箱和裝箱操作,使用ArrayList這樣的傳統(tǒng)集合對效率的影響會非常大。鑒于此,微軟提供了對泛型的支持。泛型使用一對<>括號將實際的類型括起來,然后編譯器和運行時會完成剩余的工作。 6、選擇正確的集合 要選擇正確的集合,首先需要了解一些數(shù)據(jù)結構的知識。所謂數(shù)據(jù)結構,就是相互之間存在一種或多種特定關系的數(shù)據(jù)元素的集合 說明
FCL集合圖如下: 7、確保集合的線程安全 集合線程安全是指在多個線程上添加或刪除元素時,線程之間必須保持同步。 泛型集合一般通過加鎖來進行安全鎖定,如下:
8、避免將List<T>作為自定義集合類的基類 如果要實現(xiàn)一個自定義的集合類,不應該以一個FCL集合類為基類,而應該擴展相應的泛型接口。FCL集合類應該以組合的形式包含至自定義的集合類,需擴展的泛型接口通常是IEnumer-able<T>和ICollection<T>(或ICollection<T>的子接口,如IList<T>),前者規(guī)范了集合類的迭代功能,后者則規(guī)范了一個集合通常會有的操作。 List<T>基本上沒有提供可供子類使用的protected成員(從object中繼承來的Finalize方法和Member-wiseClone方法除外),也就是說,實際上,繼承List<T>并沒有帶來任何繼承上的優(yōu)勢,反而喪失了面向接口編程帶來的靈活性。而且,稍加不注意,隱含的Bug就會接踵而至。 9、迭代器應該是只讀的 FCL中的迭代器只有GetEnumerator方法,沒有SetEnumerator方法。所有的集合類也沒有一個可寫的迭代器屬性。 原因有二
10、謹慎集合屬性的可寫操作 如果類型的屬性中有集合屬性,那么應該保證屬性對象是由類型本身產生的。如果將屬性設置為可寫,則會增加拋出異常的幾率。一般情況下,如果集合屬性沒有值,則它返回的Count等于0,而不是集合屬性的值為null。 11、使用匿名類型存儲LINQ查詢結果(最佳搭檔) 從.NET 3.0開始,C開始支持一個新特性:匿名類型。匿名類型由var、賦值運算符和一個非空初始值(或以new開頭的初始化項)組成。匿名類型有如下的基本特性:
12、在查詢中使用Lambda表達式 LINQ實際上是基于擴展方法和Lambda表達式的,理解了這一點就不難理解LINQ。任何LINQ查詢都能通過調用擴展方法的方式來替代,如下面的代碼所示: foreach(var item in personList.Select(person=>new{PersonName= person.Name,CompanyName=person.CompanyID==0?'Micro':'Sun'})) 針對LINQ設計的擴展方法大多應用了泛型委托。System命名空間定義了泛型委托Action、Func和Predicate。 可以這樣理解這三個委托:Action用于執(zhí)行一個操作,所以它沒有返回值;Func用于執(zhí)行一個操作并返回一個值;Predicate用于定義一組條件并判斷參數(shù)是否符合條件。 Select擴展方法接收的就是一個Func委托,而Lambda表達式其實就是一個簡潔的委托,運算符“=>”左邊代表的是方法的參數(shù),右邊的是方法體。 13、理解延遲求值和主動求值之間的區(qū)別 樣例如下:
在使用LINQ to SQL時,延遲求值能夠帶來顯著的性能提升。舉個例子:如果定義了兩個查詢,而且采用延遲求值,CLR則會合并兩次查詢并生成一個最終的查詢。 14、區(qū)別LINQ查詢中的IEnumerable<T>和IQueryable<T> LINQ查詢方法一共提供了兩類擴展方法,在System.Linq命名空間下,有兩個靜態(tài)類:Enumerable類,它針對繼承了IEnumerable<T>接口的集合類進行擴展;Queryable類,它針對繼承了IQueryable<T>接口的集合類進行擴展。 稍加觀察我們會發(fā)現(xiàn),接口IQueryable<T>實際也是繼承了IEnumerable<T>接口的,所以,致使這兩個接口的方法在很大程度上是一致的。那么,微軟為什么要設計出兩套擴展方法呢? 我們知道,LINQ查詢從功能上來講實際上可分為三類:LINQ to OBJECTS、LINQ to SQL、LINQ to XML(本建議不討論)。設計兩套接口的原因正是為了區(qū)別對待LINQ to OBJECTS、LINQ to SQL,兩者對于查詢的處理在內部使用的是完全不同的機制。針對LINQ to OBJECTS時,使用Enumerable中的擴展方法對本地集合進行排序和查詢等操作,查詢參數(shù)接受的是Func<>。Func<>叫做謂語表達式,相當于一個委托。針對LINQ toSQL時,則使用Queryable中的擴展方法,它接受的參數(shù)是Ex-pression<>。Expression<>用于包裝Func<>。LINQ to SQL引擎最終會將表達式樹轉化成為相應的SQL語句,然后在數(shù)據(jù)庫中執(zhí)行。 那么,到底什么時候使用IQueryable<T>,什么時候使用IEnumerable<T>呢?簡單表述就是:本地數(shù)據(jù)源用IEnumer-able<T>,遠程數(shù)據(jù)源用IQueryable<T>。 注意 在使用IQueryable<T>和IEnumerable<T>的時候還需要注意一點,IEnumerable<T>查詢的邏輯可以直接用我們自己所定義的方法,而IQueryable<T>則不能使用自定義的方法,它必須先生成表達式樹,查詢由LINQ to SQL引擎處理。在使用IQueryable<T>查詢的時候,如果使用自定義的方法,則會拋出異常。 15、使用LINQ取代集合中的比較器和迭代器 LINQ提供了類似于SQL的語法來實現(xiàn)遍歷、篩選與投影集合的功能。借助于LINQ的強大功能,我們通過兩條語句就能實現(xiàn)上述的排序要求。 var orderByBonus=from s in companySalary orderby s.Bonus select s; foreach實際會隱含調用的是集合對象的迭代器。以往,如果我們要繞開集合的Sort方法對集合元素按照一定的順序進行迭代,則需要讓類型繼承IEnumerable接口(泛型集合是IEnumerable<T>接口),實現(xiàn)一個或多個迭代器。現(xiàn)在從LINQ查詢生成匿名類型來看,相當于可以無限為集合增加迭代需求。 有了LINQ之后,我們是否就不再需要比較器和迭代器了呢?答案是否定的。我們可以利用LINQ的強大功能簡化自己的編碼,但是LINQ功能的實現(xiàn)本身就是借助于FCL泛型集合的比較器、迭代器、索引器的。LINQ相當于封裝了這些功能,讓我們使用起來更加方便。在命名空間Sys-tem.Linq下存在很多靜態(tài)類,這些靜態(tài)類存在的意義就是為FCL的泛型集合提供擴展方法
16、在LINQ查詢中避免不必要的迭代
會運用First和Take等方法,都會讓我們避免全集掃描,大大提高效率。 總結 如有需要, 上一篇的《C#規(guī)范整理·語言要素》也可以看看! |
|