前言html
軟件開發過程當中,不可避免會用到集合,C#中的集合表現爲數組和若干集合類。不論是數組仍是集合類,它們都有各自的優缺點。如何使用好集合是咱們在開發過程當中必須掌握的技巧。不要小看這些技巧,一旦在開發中使用了錯誤的集合或針對集合的方法,應用程序將會背離你的預想而運行。算法
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html 。本文主要學習記錄如下內容:數組
建議1六、元素數量可變的狀況下不該使用數組函數
建議1七、在多數狀況下使用foreach進行循環遍歷性能
建議1八、foreach不能代替for學習
建議1九、使用更有效的對象和集合初始化優化
建議1六、元素數量可變的狀況下不該使用數組this
在C#中,數組一旦被建立,長度就不能改變。若是咱們須要一個動態且可變長度的集合,就應該使用ArrayList或List<T>來建立。而數組自己,尤爲是一維數組,在遇到要求高效率的算法時,則會專門被優化以提高其效率。一維數組也成爲向量,其性能是最佳的,在IL中使用了專門的指令來處理它們。spa
從內存使用的角度來說,數組具備如下特色:pwa
一、數組在建立時被分配了一段固定長度的內存。
二、若是數組元素是值類型,則每一個元素的長度等於相應的值類型的長度
三、若是數組的元素是引用類型,則每一個元素的長度爲該引用類型的IntPtr.Size。
四、數組的存儲結構一旦被分配,就不能再變化。
而ArryaList是這樣的:
一、ArrayList是鏈表結構,能夠動態增減內存空間。
二、若是ArrayList存儲的是值類型,則會爲每一個元素增長12字節的空間,其中4字節用於對象引用,8字節是元素裝箱時引入的對象頭。
而List<T>是ArrayList的泛型實現,它省去了拆箱和裝箱帶來的開銷。
若是必定要動態改變數組的長度,一種方法是將數組轉換爲ArrayList或List<T>,以下面的代碼所示:
///定義一個一維數組 int[] iArr = { 0,1,3,4,6,7,9}; ///將數組轉換爲ArrayList ArrayList arrayListInt = ArrayList.Adapter(iArr); arrayListInt.Add(11); ///將數組轉換爲List<T> List<int> listInt = iArr.ToList<int>(); listInt.Add(11);
還有一種方法是用數組的複製功能。數組繼承自System.Array,抽象類System.Array提供了一些有用的實現方法,其中就包含了Copy方法,它負責將一個數組的內容複製到另一個數組中。不管是哪一種方法,改變數組長度就至關於從新建立了一個數組對象。
爲了讓數組看上去自己就具備動態改變長度的功能,還能夠建立一個名爲ReSize的擴展方法。
public static class ClassForExtensions { public static Array ReSize(this Array array,int newSize) { Type t = array.GetType().GetElementType(); Array newArray = Array.CreateInstance(t, newSize); Array.Copy(array, 0, newArray, 0, Math.Min(array.Length, newSize)); return newArray; } }
調用方式以下:
static void Main(string[] args) { int[] iArr = { 0,1,3,4,6,7,9}; iArr = (int[])ClassForExtensions.ReSize(iArr, 20); Console.ReadLine(); }
下面咱們來對比一下性能,先來看代碼:
class Program { static void Main(string[] args) { ResizeArray(); ResizeList(); Console.ReadLine(); } public static void ResizeArray() { int[] iArr = {0,1,3,4,6,8 }; Stopwatch watch = new Stopwatch(); watch.Start();///用於測量時間間隔 iArr = (int[])iArr.ReSize(10); watch.Stop();/// Console.WriteLine("ResizeArray:{0}", watch.Elapsed); } public static void ResizeList() { List<int> iArr = new List<int>(new int[] { 0, 1, 3, 4, 6, 8, 9 }); Stopwatch watch = new Stopwatch(); watch.Start(); iArr.Add(0); iArr.Add(0); iArr.Add(0); watch.Stop(); Console.WriteLine("ResizeList:{0}", watch.Elapsed); } }
Main函數中主要是調用,本身定義的兩個方法,第一個是從新設置數組的長度,第二個是設置List<T>的長度,經過運行時間進行測量:
嚴格意義上講,List<T>不存在改變長度的說法,此處主要是來進行對比一下,對List<T>設置長度,而且進行賦值,即使是這樣,在時間效率上ResizeList比ResizeArray要高不少不少。
建議1七、在多數狀況下使用foreach進行循環遍歷
這裏關於如何針對集合才能使用foreach進行遍歷我剛剛寫了一篇有關IEnumerable和IEnumerator兩個接口的文章,有興趣的話能夠看一下。http://www.cnblogs.com/aehyok/p/3641193.html
感受使用foreach進行循環遍歷,總共有三個好處吧:
一、提供了比較簡單、簡潔的語法。
二、自動將代碼置入try-finally塊
三、若類型實現IDispose接口,foreach會在循環結束後自動調用Dispose方法
建議1八、foreach不能代替for
foreach存在一個問題是:它不支持循環時對集合進行增刪操做。咱們來看一下簡單的例子:
List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; foreach (int item in list) { list.Remove(item); Console.WriteLine(item.ToString()); } Console.ReadLine();
一塊兒看一下執行結果:
那麼下面咱們來使用for進行嘗試:
List<int> list = new List<int>() { 1, 2, 3, 4, 5 }; for (int i = 0; i < list.Count(); i++) { list.Remove(list[i]); } Console.ReadLine();
進行刪除確定是沒問題的。可是要仔細看一下,好比它第一次刪除索引0的時候,也就是刪除了1,那麼它會當即從新調整索引,而後第二次刪除的時候,刪除的不是2,而是3這個項。那麼最終運行完發現還剩餘兩項
foreach循環使用了迭代器進行集合的遍歷,它在FCL提供的迭代器內部維護了一個對集合版本的控制。那麼什麼是集合版本呢?簡單的說,其實它就是一個整型的變量,任何對集合的增刪操做都會使版本號加1。foreach循環會調用MoveNext方法來遍歷元素,在MoveNext方法內部會進行版本號的檢測,一旦檢測到版本號有變更,就會拋出InvalidOperationException異常。
若是使用for循環就不會帶來這樣的問題。for直接使用所引器,它不對集合版本號進行判斷,因此不存在由於集合的變更而帶來的異常(固然,超出索引長度這種狀況除外)。
索引,由於版本檢測的緣故,foreach循環並不能帶起for循環。
建議1九、使用更有效的對象和集合初始化
對象初始化設定項支持能夠直接在大括號中對自動實現的屬性進行賦值。
class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { Person person = new Person() { Name = "aehyok", Age = 25 }; Console.ReadLine(); } }
以往只能依靠構造方法傳值進去,或者在對象構造完畢後對屬性進行賦值。如今這些步驟簡化了,初始化設定項實際至關於編譯器在對象生成後對屬性進行了賦值。
class Person { public string Name { get; set; } public int Age { get; set; } } class Program { static void Main(string[] args) { Person person = new Person() { Name = "Kris", Age = 22 }; List<Person> personList = new List<Person>() { new Person() { Name = "aehyok", Age = 25 }, person, null }; Console.ReadLine(); } }
使用集合的初始化設定項,編譯器會在集合對象建立完畢後對集合調用Add方法。上面這段代碼展現瞭如何在初始化語句中建立一個新對象或一個現有對象,以及一個null值。
不過,初始化設定項毫不僅僅是爲了對象和集合初始化的方便,它更重要的做用是爲LINQ查詢中的匿名類型進行屬性的初始化。因爲LINQ查詢返回的集合中匿名類型的屬性都是隻讀的,若是須要爲匿名類型屬性賦值,或者增長屬性,只能經過初始化設定項來進行。初始化設定項還能爲屬性使用表達式。
來看一段代碼:
List<Person> lst = new List<Person>() { new Person(){ Age = 10,Name="Tommy"}, new Person(){ Age = 20,Name="Sammy"} }; var entity = from p in lst select new { p.Name, AgeScope = p.Age > 10 ? "Old" : "Young" }; foreach (var item in entity) { Response.Write(string.Format("name is {0},{1}", item.Name, item.AgeScope)); }
AgeScope 屬性是通過計算得出的,有了如此方便的初始化方式,使得代碼更加優雅靈活。