一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

C# 規(guī)范整理 集合和Linq

 蘭亭文藝 2019-07-15
作者:天空的湛藍
鏈接:https://www.cnblogs.com/zhan520g/p/11018163.html

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ù)組成為一個大對象。

  1. 如果一定要動態(tài)改變數(shù)組的長度,一種方法是將數(shù)組轉換為ArrayList或List<T>,需要擴容時,內部數(shù)組將自動翻倍擴容

  2. 還有一種方法是用數(shù)組的復制功能。數(shù)組繼承自System.Array,抽象類System.Array提供了一些有用的實現(xiàn)方法,其中就包含了方法,它負責將一個數(shù)組的內容復制到另外一個數(shù)組中。無論是哪種方法,改變數(shù)組長度就相當于重新創(chuàng)建了一個數(shù)組對象。

2、多數(shù)情況下使用foreach進行循環(huán)遍歷

采用foreach最大限度地簡化了代碼。

它用于遍歷一個繼承了IEmuerable或IEmuerable<T>接口的集合元素。借助于IL代碼可以看到foreach還是本質就是利用了迭代器來進行集合遍歷。如下:

List<object>list=new List<object>();
using(List<object>.Enumerator CS$5$0000=list.GetEnumerator())
{
while(CS$5$0000.MoveNext())
{
object current=CS$5$0000.Current;
}
}

除了代碼簡潔之外,foreach還有兩個優(yōu)勢

  • 自動將代碼置入try-finally塊

  • 若類型實現(xiàn)了IDispose接口,它會在循環(huán)結束后自動調用Dispose方法。

3、foreach不能代替for

foreach存在的一個問題是:它不支持循環(huán)時對集合進行增刪操作。取而代之的方法是使用for循環(huán)。

不支持原因:

  • foreach循環(huán)使用了迭代器進行集合的遍歷,它在FCL提供的迭代器內部維護了一個對集合版本的控制。那么什么是集合版本?簡單來說,其實它就是一個整型的變量,任何對集合的增刪操作都會使版本號加1。foreach循環(huán)會調用MoveNext方法來遍歷元素,在MoveNext方法內部會進行版本號的檢測,一旦檢測到版本號有變動,就會拋出InvalidOperationException異常。

  • 如果使用for循環(huán)就不會帶來這樣的問題。for直接使用索引器,它不對集合版本號進行判斷,所以不存在因為集合的變動而帶來的異常(當然,超出索引長度這種情況除外)。

public bool MoveNext()
{
List<T>list=this.list;
if((this.version==list._version)&&(this.index<list._size))
{
this.current=list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}

無論是for循環(huán)還是foreach循環(huán),內部都是對該數(shù)組的訪問,而迭代器僅僅是多進行了一次版本檢測。事實上,在循環(huán)內部,兩者生成的IL代碼也是差不多的。

4、使用更有效的對象和集合初始化

舉例:

class Program {
static void Main(string[]args)
{

Person person=new Person(){Name='Mike',Age=20};
}
}

class Person
{
public string Name{get;set;}
public int Age{get;set;}
}

對象初始化設定項支持在大括號中對自動實現(xiàn)的屬性進行賦值。以往只能依靠構造方法傳值進去,或者在對象構造完畢后對屬性進行賦值。現(xiàn)在這些步驟簡化了,初始化設定項實際相當于編譯器在對象生成后對屬性進行了賦值。

集合初始化也同樣進行了簡化:

List<Person>personList=new List<Person>( )
{
new Person() {Name='Rose',Age=19},
mike,
null
};

重點:初始化設定項絕不僅僅是為了對象和集合初始化的方便,它更重要的作用是為LINQ查詢中的匿名類型進行屬性的初始化。由于LINQ查詢返回的集合中匿名類型的屬性都是只讀的,如果需要為匿名類型屬性賦值,或者增加屬性,只能通過初始化設定項來進行。初始化設定項還能為屬性使用表達式。

舉例

List<Person>personList2=new List<Person>()
{
new Person(){Name='Rose',Age=19},
new Person(){Name='Steve',Age=45},
new Person(){Name='Jessica',Age=20}
};

var pTemp=from p in personList2
select new {p.Name, AgeScope=p.Age>20?'Old':'Young'};

foreach(var item in pTemp)
{
Console.WriteLine(string.Format('{0}:
{1}',item.Name,item.AgeScope));
}

5、使用泛型集合代替非泛型集合

注意,非泛型集合在System.Collections命名空間下,對應的泛型集合則在System.Collections.Generic命名空間下。

泛型的好處不言而喻,,如果對大型集合進行循環(huán)訪問、轉型或拆箱和裝箱操作,使用ArrayList這樣的傳統(tǒng)集合對效率的影響會非常大。鑒于此,微軟提供了對泛型的支持。泛型使用一對<>括號將實際的類型括起來,然后編譯器和運行時會完成剩余的工作。

6、選擇正確的集合

要選擇正確的集合,首先需要了解一些數(shù)據(jù)結構的知識。所謂數(shù)據(jù)結構,就是相互之間存在一種或多種特定關系的數(shù)據(jù)元素的集合

說明

  1. 直接存儲結構的優(yōu)點是:向數(shù)據(jù)結構中添加元素是很高效的,直接放在數(shù)據(jù)末尾的第一個空位上就可以了。它的缺點是:向集合插入元素將會變得低效,它需要給插入的元素騰出位置并順序移動后面的元素。

    如果集合的數(shù)目固定并且不涉及轉型,使用數(shù)組效率高,否則就使用List<T>(該使用數(shù)組的時候,還是要使用數(shù)組)

  2. 順序存儲結構,即線性表。線性表可動態(tài)地擴大和縮小,它在一片連續(xù)的區(qū)域中存儲數(shù)據(jù)元素。線性表不能按照索引進行查找,它是通過對地址的引用來搜索元素的,為了找到某個元素,它必須遍歷所有元素,直到找到對應的元素為止。所以,線性表的優(yōu)點是插入和刪除數(shù)據(jù)效率高,缺點是查找的效率相對來說低一些。

  3. 隊列Queue<T>遵循的是先入先出的模式,它在集合末尾添加元素,在集合的起始位置刪除元素。

  4. 棧Stack<T>遵循的是后入先出的模式,它在集合末尾添加元素,同時也在集合末尾刪除元素。

  5. 字典Dictionary<TKey, TValue>存儲的是鍵值對,值在基于鍵的散列碼的基礎上進行存儲。字典類對象由包含集合元素的存儲桶組成,每一個存儲桶與基于該元素的鍵的哈希值關聯(lián)。如果需要根據(jù)鍵進行值的查找,使用Dictionary<TKey, TValue>將會使搜索和檢索更快捷。

  6. 雙向鏈表LinkedList<T>是一個類型為LinkedListNode的元素對象的集合。當我們覺得在集合中插入和刪除數(shù)據(jù)很慢時,就可以考慮使用鏈表。如果使用LinkedList<T>,我們會發(fā)現(xiàn)此類型并沒有其他集合普遍具有的Add方法,取而代之的是AddAfter、AddBefore、AddFirst、AddLast等方法。雙向鏈表中的每個節(jié)點都向前指向Previous節(jié)點,向后指向Next節(jié)點。

  7. 在FCL中,非線性集合實現(xiàn)得不多。非線性集合分為層次集合和組集合。層次集合(如樹)在FCL中沒有實現(xiàn)。組集合又分為集和圖,集在FCL中實現(xiàn)為HashSet<T>,而圖在FCL中也沒有對應的實現(xiàn)。

    集的概念本意是指存放在集合中的元素是無序的且不能重復的。

  8. 除了上面提到的集合類型外,還有其他幾個要掌握的集合類型,它們是在實際應用中發(fā)展而來的對以上基礎類型的擴展:SortedList<T>、SortedDictionary<TKey, TValue>、Sorted-Set<T>。它們所擴展的對應類分別為List<T>、Dictionary<TKey, TValue>、HashSet<T>,作用是將原本無序排列的元素變?yōu)橛行蚺帕小?/p>

  9. 除了排序上的需求增加了上面3個集合類外,在命名空間System.Collections.Concurrent下,還涉及幾個多線程集合類。它們主要是:

  • ConcurrentBag<T>對應List<T>

  • ConcurrentDictionary<TKey, TValue>對應Dictionary<TKey, TValue>

  • ConcurrentQueue<T>對應Queue<T>

  • ConcurrentStack<T>對應Stack<T>

FCL集合圖如下:

7、確保集合的線程安全

集合線程安全是指在多個線程上添加或刪除元素時,線程之間必須保持同步。

泛型集合一般通過加鎖來進行安全鎖定,如下:

static object sycObj=new object();
static void Main(string[]args)
{
//object sycObj=new object();
Thread t1=new Thread(()=>{

//確保等待t2開始之后才運行下面的代碼
autoSet.WaitOne();
lock(sycObj)
{
foreach(Person item in list)
{
Console.WriteLine('t1:'+item.Name);
Thread.Sleep(1000);
}
}
}

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方法。所有的集合類也沒有一個可寫的迭代器屬性。

原因有二

  1. 這違背了設計模式中的開閉原則。被設置到集合中的迭代器可能會直接導致集合的行為發(fā)生異?;蜃儎印R坏┐_實需要新的迭代需求,完全可以創(chuàng)建一個新的迭代器來滿足需求,而不是為集合設置該迭代器,因為這樣做會直接導致使用到該集合對象的其他迭代場景發(fā)生不可知的行為。

  2. 現(xiàn)在,我們有了LINQ。使用LINQ可以不用創(chuàng)建任何新的類型就能滿足任何的迭代需求。

10、謹慎集合屬性的可寫操作

如果類型的屬性中有集合屬性,那么應該保證屬性對象是由類型本身產生的。如果將屬性設置為可寫,則會增加拋出異常的幾率。一般情況下,如果集合屬性沒有值,則它返回的Count等于0,而不是集合屬性的值為null。

11、使用匿名類型存儲LINQ查詢結果(最佳搭檔)

從.NET 3.0開始,C開始支持一個新特性:匿名類型。匿名類型由var、賦值運算符和一個非空初始值(或以new開頭的初始化項)組成。匿名類型有如下的基本特性:

  • 既支持簡單類型也支持復雜類型。簡單類型必須是一個非空初始值,復雜類型則是一個以new開頭的初始化項;

  • 匿名類型的屬性是只讀的,沒有屬性設置器,它一旦被初始化就不可更改;

  • 如果兩個匿名類型的屬性值相同,那么就認為兩個匿名類型相等;

  • 匿名類型可以在循環(huán)中用作初始化器;

  • 匿名類型支持智能感知;

  • 還有一點,雖然不常用,但是匿名類型確實也可以擁有方法。

12、在查詢中使用Lambda表達式

LINQ實際上是基于擴展方法和Lambda表達式的,理解了這一點就不難理解LINQ。任何LINQ查詢都能通過調用擴展方法的方式來替代,如下面的代碼所示:

foreach(var item in personList.Select(person=>new{PersonName= person.Name,CompanyName=person.CompanyID==0?'Micro':'Sun'}))
{
Console.WriteLine(string.Format('{0} :{1}',item.PersonName, item.CompanyName));
}

針對LINQ設計的擴展方法大多應用了泛型委托。System命名空間定義了泛型委托Action、Func和Predicate。

可以這樣理解這三個委托:Action用于執(zhí)行一個操作,所以它沒有返回值;Func用于執(zhí)行一個操作并返回一個值;Predicate用于定義一組條件并判斷參數(shù)是否符合條件。

Select擴展方法接收的就是一個Func委托,而Lambda表達式其實就是一個簡潔的委托,運算符“=>”左邊代表的是方法的參數(shù),右邊的是方法體。

13、理解延遲求值和主動求值之間的區(qū)別

樣例如下:

List<int>list=new List<int>(){0,1,2,3,4,5,6,7,8,9};
var temp1=from c in list where c>5 select c;
var temp2=(from c in list where c>5 select c).ToList<int>();

在使用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的泛型集合提供擴展方法

  • 強烈建議你利用LINQ所帶來的便捷性,但我們仍需掌握比較器、迭代器、索引器的原理,以便更好地理解LINQ的思想,寫出更高質量的代碼。最好是能看懂Linq源碼。

public static IOrderedEnumerable<TSource>OrderBy<TSource,TKey>(this    IEnumerable<TSource>source,Func<TSource,TKey>keySelector){    //省略}

16、在LINQ查詢中避免不必要的迭代

  1. 比如常使用First()方法,F(xiàn)irst方法實際完成的工作是:搜索到滿足條件的第一個元素,就從集合中返回。如果沒有符合條件的元素,它也會遍歷整個集合。

  2. 與First方法類似的還有Take方法,Take方法接收一個整型參數(shù),然后為我們返回該參數(shù)指定的元素個數(shù)。與First一樣,它在滿足條件以后,會從當前的迭代過程直接返回,而不是等到整個迭代過程完畢再返回。如果一個集合包含了很多的元素,那么這種查詢會為我們帶來可觀的時間效率。

會運用First和Take等方法,都會讓我們避免全集掃描,大大提高效率。

總結

如有需要, 上一篇的《C#規(guī)范整理·語言要素》也可以看看!

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    久久久精品日韩欧美丰满 | 日韩精品你懂的在线观看| 亚洲最新av在线观看| 午夜免费精品视频在线看| 台湾综合熟女一区二区| 欧美一区二区三区喷汁尤物| 欧美日韩国产精品自在自线| 亚洲精品国产第一区二区多人| 国产成人在线一区二区三区 | 国产熟女一区二区不卡| 国产成人免费高潮激情电| 欧美一级不卡视频在线观看| 麻豆视传媒短视频免费观看| 日韩三极片在线免费播放| 少妇福利视频一区二区| 国产精品熟女在线视频| 成人精品网一区二区三区| 亚洲精品一二三区不卡| 国产又粗又硬又长又爽的剧情| 国产成人av在线免播放观看av | 欧美一区日韩一区日韩一区| 亚洲精品一区二区三区免 | 在线免费观看黄色美女| 日本人妻的诱惑在线观看| 成人国产激情在线视频| 国产一区二区三区午夜精品| 亚洲人妻av中文字幕| 欧美日韩国产精品第五页| 欧洲一区二区三区蜜桃| 91久久精品国产成人| 正在播放国产又粗又长| 中文字幕一区二区熟女| 亚洲一区二区三区精选| 国产又黄又猛又粗又爽的片| 男人把女人操得嗷嗷叫| 久久99一本色道亚洲精品| 少妇激情在线免费观看| 又色又爽又无遮挡的视频 | 自拍偷拍福利视频在线观看| 99国产精品国产精品九九| 欧美一区二区三区在线播放|