數(shù)組是允許將多個數(shù)據項當作一個集合來處理的機制。CLR支持一維數(shù)組、多維數(shù)組和交錯數(shù)據(即由數(shù)組構成的數(shù)組)。所有數(shù)組類型都隱式地從System.Array抽象類派生,后者又派生自System.Object。這意味著數(shù)組始終是引用類型,是在托管堆上分配的。在你應用程序的變量或字段中,包含的是對數(shù)組的引用,而不是包含數(shù)組本身的元素。下面的代碼更清楚的說明了這一點:
Int32[] myIntegers; //聲明一個數(shù)組引用myIntegers = new int32[100] //創(chuàng)建含有100個Int32的數(shù)組
在第一行代碼中,myIntegers變量能指向一個一維數(shù)組(由Int32值構成)。myIntegers剛開始被設為null,因為當時還沒有分配數(shù)組。第二行代碼分配了含有100個Int32值的一個數(shù)組,所有Int32都被初始化為0。由于數(shù)組是引 用類型,所有托管堆上還包含一個未裝箱Int32所需要的內存塊。實際上,除了數(shù)組元素,數(shù)字對象占據的內存塊還包含一個類型對象指針、一個同步塊索引和一些額外的成員(overhead)。該數(shù)組的內存塊地址被返回并保存到myIntegers變量中。
C#也支持多維數(shù)組。下面演示了幾個多維數(shù)組的例子:
// 創(chuàng)建一個二維數(shù)組,由Double值構成Double[,] myDoubles = new Double[10,20];// 創(chuàng)建一個三位數(shù)組,由String引用構成String[,,] myStrings = new String[5,3,10];
CLR還支持交錯數(shù)組,即由數(shù)組構成的數(shù)組。下面例子演示了如何創(chuàng)建一個多邊形數(shù)組,其中每一個多邊形都由一個Point實例數(shù)組構成。
// 創(chuàng)建一個含有Point數(shù)組的一維數(shù)組Point[][] myPolygons = new Point[3][];// myPolygons[0]引用一個含有10個Point實例的數(shù)組myPolygons[0] = new Point[10];// myPolygons[1]引用一個含有20個Point實例的數(shù)組myPolygons[1] = new Point[20];// myPolygons[2]引用一個含有30個Point實例的數(shù)組myPolygons[2] = new Point[30];// 顯示第一個多邊形中的Pointfor (Int32 x =0 ; x < myPolygons[0].Length; x++){ Console.WriteLine(myPolygons[0][x]);}注意:CLR會驗證數(shù)組索引的有效性。換句話說,不能創(chuàng)建一個含有100個元素的數(shù)組(索引編號為0到99),又試圖訪問索引為-5或100的元素。
一、始化數(shù)組元素
前面展示了如何創(chuàng)建一個數(shù)組對象,以及如何初始化數(shù)組中的元素。C#允許用一個語句來同時做兩件事。例如:
String[] names = new String[] { "Aidan", "Grant" };大括號中的以逗號分隔的數(shù)據成為數(shù)組初始化器。每個數(shù)據項都可以是一個任意復雜度的表達式;在多維數(shù)組的情況下,則可以是一個嵌套的數(shù)組初始化器。可利用C#的隱式類型的數(shù)組功能讓編譯器推斷數(shù)組元素的類型。注意,下面這一行代碼沒有在new和[]之間指定類型:
var names = new[] { "Aidan", "Grant", null};在上一行中,編譯器檢查數(shù)組中用于初始化數(shù)組元素的表達式的類型,并選擇所有元素最接近的共同基類作為數(shù)組的類型。在本例中,編譯器發(fā)現(xiàn)兩個String和一個null。由于null可隱式轉型成為任意引用類型(包括String),所以編譯器推斷應該創(chuàng)建和初始化一個由String引用構成的數(shù)組。
給定一下代碼:
var names = new[] { "Aidan", "Grant", 123};編譯器是會報錯的,雖然String類和Int32共同基類是Object,意味著編譯器不得不創(chuàng)建Object引用了一個數(shù)組,然后對123進行裝箱,并讓最后一個數(shù)組元素引用已裝箱的,值為123的一個Int32。但C#團隊認為,隱式對數(shù)組元素進行裝箱是一個代價昂貴的操作,所以要做編譯時報錯。
在C#中還可以這樣初始化數(shù)組:
String[] names = { "Aidan", "Grant" };但是C#不允許在這種語法中使用隱式類型的局部變量:
var names = { "Aidan", "Grant" };最后來看下"隱式類型的數(shù)組"如何與"匿名類型"和"隱式類型的局部變量"組合使用。
// 使用C#的隱式類型的局部變量、隱式類型的數(shù)組和匿名類型var kids = new[] {new { Name="Aidan" }, new { Name="Grant" }};// 示例用法foreach (var kid in kids) Console.WriteLine(kid.Name);輸出結果:
AidanGrant
二、數(shù)組轉型 對于元素為引用類型的數(shù)組,CLR允許將數(shù)組元素從一種類型隱式轉型到另一種類型。為了成功轉型,兩個數(shù)組類型必須維數(shù)相等,而且從源類型到目標類型,必須存在一個隱式或顯示轉換。CLR不允許將值類型元素的數(shù)組轉型為其他任何類型。(不過為了模擬實現(xiàn)這種效果,可利用Array.Copy方法創(chuàng)建一個新數(shù)組并在其中填充數(shù)據)。下面演示了數(shù)組轉型過程:
PRivate static void ArrayCasting() {// 創(chuàng)建一個二維FileStream數(shù)組FileStream[,] fs2dim = new FileStream[5, 10];// 隱式轉型為一個二維Object數(shù)組Object[,] o2dim = fs2dim;// 不能從二維數(shù)組轉型為一維數(shù)組//Stream[] s1dim = (Stream[]) o2dim;// 顯式轉型為二維Stream數(shù)組Stream[,] s2dim = (Stream[,]) o2dim;// 顯式轉型為二維String數(shù)組// 能通過編譯,但在運行時會拋出異常String[,] st2dim = (String[,]) o2dim;// 創(chuàng)建一個意味Int32數(shù)組(元素是值類型)Int32[] i1dim = new Int32[5];// 不能將值類型的數(shù)組轉型為其他任何類型// Object[] o1dim = (Object[]) i1dim;// 創(chuàng)建一個新數(shù)組,使用Array.Copy將元數(shù)組中的每一個元素// 轉型為目標數(shù)組中的元素類型,并把它們復制過去// 下面的代碼創(chuàng)建一個元素為引用類型的數(shù)組,// 每個元素都是對已裝箱的Int32的引用Object[] o1dim = new Object[i1dim.Length];Array.Copy(i1dim, o1dim, 0);}
Array.Copy方法的作用不僅僅是將元素從一個數(shù)組復制到另一個數(shù)組。Copy方法還能正確處理內存的重疊區(qū)域。
Copy方法還能在復制每一個數(shù)組元素時進行必要的類型轉換。Copy方法能執(zhí)行以下轉換:1)將值類型的元素裝箱為引用類型的元素,比如將一個Int32[]復制到一個Object[]中。2)將引用類型的元素拆箱為值類型的元素,比如將一個Object[]復制到Int32[]中。3)加寬CLR基元值類型,比如將一個Int32[]的元素復制到一個Double[]中。4)在兩個數(shù)組之間復制時,如果僅從數(shù)組類型證明不了兩者的兼容性。
在某些情況下,將數(shù)組從一種類型轉換為另一種類型是非常有用的。這種功能稱為數(shù)據協(xié)變性。利用數(shù)組協(xié)變性時,應該清楚由此帶來的性能損失。
注意:如果只需要把數(shù)組中某些元素復制到另一個數(shù)組,可以選擇System.Buffer的BlockCopy方法,它的執(zhí)行速度比Array.Copy方法快。不過,Buffer的BlockCopy方法只支持基元類型,不提供像Array的Copy方法那樣的轉型能力。方法的Int32參數(shù)代表的是數(shù)組中的字節(jié)偏移量,而非元素索引。如果需要可靠的將一個數(shù)組中的元素復制到另一個數(shù)組,應該使用System.Array的ConstrainedCopy方法,該方法能保證不破壞目標數(shù)組中的數(shù)組的前提下完成復制,或者拋出異常。另外,它不執(zhí)行任何裝箱、拆箱或向下類型轉換。
三、所有數(shù)組都隱式派生自System.Array
如果像下面這樣聲明一個數(shù)組變量:
FileStream[] fsArray;
CLR會為AppDomain自動創(chuàng)建一個FileStream[]類型。這個類型將隱式派生自System.Array類型;因此,System.Array類型定義的所有實例方法和屬性都將有FileStream[]繼承,使這些方法和屬性能通過fsArray變量調用。
四、所有數(shù)組都隱式實現(xiàn)IEnumerable,ICollection和IList
許多方法都能操作各種集合對象,因為在聲明它們時,使用了IEnumerable,ICollection和IList等參數(shù)。可以將數(shù)組傳給這些方法,因為System.Array也實現(xiàn)了這三個接口。System.Array之所以實現(xiàn)這些非泛型接口,是因為這些接口將所有元素都視為Systm.Object。然而,最好讓System.Array實現(xiàn)這個接口的泛型形式,提供更好的編譯時類型安全性和更好的性能。
五、數(shù)組的傳遞和返回 數(shù)組作為實參傳給一個方法時,實際傳遞的是對該數(shù)組的引用。因此,被調用的方法能修改數(shù)組中的元素。如果不想被修改,必須生成數(shù)組的一個拷貝,并將這個拷貝傳給方法。注意,Array.Copy方法執(zhí)行的是淺拷貝。
有的方法返回一個對數(shù)組的引用。如果方法構造并初始化數(shù)組,返回數(shù)組引用是沒有問題的。但假如方法返回的是對一個字段維護的內部數(shù)組的引用,就必須決定是否向讓該方法的調用者直接訪問這個數(shù)組及其元素。如果是就可以返回數(shù)組引用。但是通常情況下,你并不希望方法的調用這獲得這個訪問權限。所以,方法應該構造一個新數(shù)組,并調用Array.Copy返回對新數(shù)組的一個引用。
如果定義一個返回數(shù)組引用的方法,而且該數(shù)組不包含元素,那么方法既可以返回null,又可以放回對包含另個元素的一個數(shù)組的引用。實現(xiàn)這種方法時,Microsoft強烈建議讓它返回后者,因為這樣做能簡化調用該方法時需要的代碼。
// 這段代碼更容易寫,更容易理解Appointment[] app = GetAppointmentForToday();for (Int32 a =0; a< app.Length; a++) {// 對app[a]執(zhí)行操作}如果返回null的話:
// 寫起來麻煩,不容易理解Appointment[] app = GetAppointmentForToday();if( app !=null ) {for (Int32 a =0; a< app.Length; a++) {// 對app[a]執(zhí)行操作 }}六、創(chuàng)建下限非零的數(shù)組
可以調用數(shù)組的靜態(tài)CreateInstance方法來動態(tài)創(chuàng)建自己的數(shù)組。該方法有若干個重載版本,允許指定數(shù)組元素的類型、數(shù)組的維數(shù)、每一維的下限和每一維的元素數(shù)目。CreateInstance為數(shù)組分配內存,將參數(shù)信息保存到數(shù)組的內存塊的額外開銷(overhead)部分。然后返回對該數(shù)組的一個引用。
七、數(shù)組的訪問性能 CLR內部實際支持兩種不同的數(shù)組 1)下限為0的意味數(shù)組。這些數(shù)組有時稱為SZ數(shù)組或向量。 2)下限未知的一維或多維數(shù)組。 可執(zhí)行一下代碼來實際地查看不同種類的輸出
internal static class ArrayTypes {public static void Go() {Array a;// 創(chuàng)建一個一維數(shù)組的0基數(shù)組,其中不包含任何元素a = new String[0]; Console.WriteLine(a.GetType()); // System.String[]// 創(chuàng)建一個一維數(shù)組的0基數(shù)組,其中不包含任何元素a = Array.CreateInstance(typeof(String), new Int32[] { 0 }, new Int32[] { 0 }); Console.WriteLine(a.GetType()); // System.String[]// 創(chuàng)建一個一維數(shù)組的1基數(shù)組,其中不包含任何元素a = Array.CreateInstance(typeof(String), new Int32[] { 0 }, new Int32[] { 1 }); Console.WriteLine(a.GetType()); // System.String[*] <-- 注意!Console.WriteLine();// 創(chuàng)建一個二維數(shù)組的0基數(shù)組,其中不包含任何元素a = new String[0, 0]; Console.WriteLine(a.GetType()); // System.String[,]// 創(chuàng)建一個二維數(shù)組的0基數(shù)組,其中不包含任何元素a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[] { 0, 0 }); Console.WriteLine(a.GetType()); // System.String[,]// 創(chuàng)建一個二維數(shù)組的1基數(shù)組,其中不包含任何元素a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[] { 1, 1 }); Console.WriteLine(a.GetType()); // System.String[,] }}對于一維數(shù)組,0基數(shù)組顯示的類型名稱是System.String[],但1基數(shù)組顯示的是System.String[*]。*符號表示CLR知道該數(shù)組不是0基的。注意,C#不允許聲明String[*]類型的變量,因此不能使用C#語法來訪問一維的非0基數(shù)組。盡管可以調用Array的GetValue和SetValue方法來訪問數(shù)組的元素,但速度會比較慢,畢竟有方法調用的開銷。
對于多維數(shù)組,0基和1基數(shù)組會顯示同樣的類型名稱:System.String[,]。在運行時,CLR將對所有多維數(shù)組都視為非0基數(shù)組。這自然會人覺得應該顯示為System.String[*,*]。但是,對于多維數(shù)組,CLR決定不用*符號,避免開發(fā)人員對*產生混淆。 訪問一維0基數(shù)組的元素比訪問非0基數(shù)組或多維數(shù)組的元素稍快一些。首先,有一些特殊的IL指令,比如newarr,ldelem,ldelema等用于處理一維0基數(shù)組,這些特殊IL指令會導致JIT編譯器生成優(yōu)化代碼。其次,JIT編譯器知道for循環(huán)要反問0到Length-1之間的數(shù)組元素。所以,JIT編譯器生成的代碼會在運行時測試所有數(shù)組元素的訪問都在數(shù)組有效訪問內。
如果很關系性能,請考慮由數(shù)組構成的數(shù)組(即交錯數(shù)組)來替代矩形數(shù)組。
下面C#代碼演示了訪問二維數(shù)組的三種方式:
internal static class MultiDimArrayPerformance{private const Int32 c_numElements = 10000;public static void Go(){const Int32 testCount = 10;Stopwatch sw;// 聲明一個二維數(shù)組Int32[,] a2Dim = new Int32[c_numElements, c_numElements];// 將一個二維數(shù)組聲明為交錯數(shù)組Int32[][] aJagged = new Int32[c_numElements][];for (Int32 x = 0; x < c_numElements; x++)aJagged[x] = new Int32[c_numElements];// 1: 用普通的安全技術訪問數(shù)組中的所有元素sw = Stopwatch.StartNew();for (Int32 test = 0; test < testCount; test++)Safe2DimArrayaccess(a2Dim);Console.WriteLine("{0}: Safe2DimArrayAccess", sw.Elapsed);// 2: 用交錯數(shù)組技術訪問數(shù)組中的所有元素sw = Stopwatch.StartNew();for (Int32 test = 0; test < testCount; test++)SafeJaggedArrayAccess(aJagged);Console.WriteLine("{0}: SafeJaggedArrayAccess", sw.Elapsed);// 3: 用unsafe訪問數(shù)組中的所有元素sw = Stopwatch.StartNew();for (Int32 test = 0; test < testCount; test++)Unsafe2DimArrayAccess(a2Dim);Console.WriteLine("{0}: Unsafe2DimArrayAccess", sw.Elapsed);Console.ReadLine();}private static Int32 Safe2DimArrayAccess(Int32[,] a){ Int32 sum = 0; for (Int32 x = 0; x < c_numElements; x++) { for (Int32 y = 0; y < c_numElements; y++) { sum += a[x, y]; } } return sum;}private static Int32 SafeJaggedArrayAccess(Int32[][] a){ Int32 sum = 0; for (Int32 x = 0; x < c_numElements; x++) { for (Int32 y = 0; y < c_numEleme
新聞熱點
疑難解答