前言
軟體開發程序中,不可避免會用到集合,C#中的集合表現為陣列和若干集合類,不管是陣列還是集合類,它們都有各自的優缺點,如何使用好集合是我們在開發程序中必須掌握的技巧,不要小看這些技巧,一旦在開發中使用了錯誤的集合或針對集合的方法,應用程式將會背離你的預想而運行,
本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html ,本文主要學習記錄以下內容:
建議16、元素數量可變的情況下不應使用陣列
建議17、在多數情況下使用foreach進行回圈遍歷
建議18、foreach不能代替for
建議19、使用更有效的物件和集合初始化
建議16、元素數量可變的情況下不應使用陣列
在C#中,陣列一旦被創建,長度就不能改變,如果我們需要一個動態且可變長度的集合,就應該使用ArrayList或List<T>來創建,而陣列本身,尤其是一維陣列,在遇到要求高效率的演算法時,則會專門被優化以提升其效率,一維陣列也成為向量,其性能是最佳的,在IL中使用了專門的指令來處理它們,
從記憶體使用的角度來講,陣列具有以下特點:
1、陣列在創建時被分配了一段固定長度的記憶體,
2、如果陣列元素是值型別,則每個元素的長度等于相應的值型別的長度
3、如果陣列的元素是參考型別,則每個元素的長度為該參考型別的IntPtr.Size,
4、陣列的存盤結構一旦被分配,就不能再變化,
而ArryaList是這樣的:
1、ArrayList是鏈表結構,可以動態增減記憶體空間,
2、如果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要高很多很多,
建議17、在多數情況下使用foreach進行回圈遍歷
這里關于如何針對集合才能使用foreach進行遍歷我剛剛寫了一篇有關IEnumerable和IEnumerator兩個介面的文章,有興趣的話可以看一下,http://www.cnblogs.com/aehyok/p/3641193.html
感覺使用foreach進行回圈遍歷,總共有三個好處吧:
1、提供了比較簡單、簡潔的語法,
2、自動將代碼置入try-finally塊

3、若型別實作IDispose介面,foreach會在回圈結束后自動呼叫Dispose方法
建議18、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回圈,
建議19、使用更有效的物件和集合初始化
物件初始化設定項支持可以直接在大括號中對自動實作的屬性進行賦值,
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 屬性是經過計算得出的,有了如此方便的初始化方式,使得代碼更加優雅靈活,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/93253.html
標籤:C#
上一篇:.Neter所應該徹底了解的委托
