如果需要使用同一類型的多個對象,可以使用數組和集合(后面介紹)。C#用特殊的記號聲明,初始化和使用數組。Array類在后台發揮作用,它為數組中的元素排序和過濾提供了多個方法。使用枚舉器,可以迭代數組中的所有元素。
如果需要使用不同類型的多個對象,可以使用Tuple(元組)類型。
一.簡單數組(一維數組)
數組是一種數據結構,它可以包含同一個類型的多個元素。
1.數組的聲明
在聲明數組時,先定義數組中的元素類型,其后是一對空方括號和一個變量名。
int[] myArray;
2.數組的初始化
聲明了數組之后,就必須為數組分配內存,以保存數組的所有元素。數組是引用類型,所以必須給它分配堆上的內存。為此,應使用new運算符,指定數組中元素的類型和數量來初始化數組的變量。
myArray = new int[4];
在聲明和初始化數組后,變量myArray就引用了4個整數值,它們位於托管堆上:
在指定了數組的大小后,就不能重新設置數組的大小。如果事先不知道數組中應包含多少個元素,就可以使用集合。
除了在兩個語句中聲明和初始化數組之外,還可以在一個語句中聲明和初始化數組:
int[] myArray = new int[4];
還可以使用數組初始化器為數組的每個元素復制。數組初始化器只能在聲明數組變量時使用,不能在聲明數組之后使用。
int[] myArray = new int[4]{1,3,5,7};
如果用花括號初始化數組,可以不指定數組的大小,因為編譯器會自動統計元素的個數:
int[] myArray = new int[]{1,3,5,7};
也可以使用更簡單的形式:
int[] myArray = {1,3,5,7};
3.訪問數組元素
在聲明和初始化數組之后,就可以使用索引器訪問其中的元素了。數組只支持有整型參數的索引器。
索引器總是以0開頭,表示第一個元素。可以傳遞給索引器的最大值是元素個數減1,因為索引從0開始:
int[] myArray = {1,3,5,7};
int v1 = myArray[0];
int v2 = myArray[1];
myArray[3] = 4;
可以使用數組的Length屬性獲取元素的個數。
4.數組中使用引用類型
數組除了能聲明預定義類型的數組,還可以聲明自定義類型的數組。
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
Person[] myPersons = new Person[2];
myPersons[0] = new Person { FirstName = "Ayrton", LastName = "Senna" };
myPersons[1] = new Person { FirstName = "Michael", LastName = "Schumacher" };
如果數組中的元素是引用類型,就必須為每個數組元素分配內存。如果使用了數組中未分配內存的元素,就會拋出NullReferenceException類型的異常。
下面是內存情況:
對自定義類型也可以使用數組初始化器:
Person[] myPersons2 =
{
new Person { FirstName="Ayrton", LastName="Senna"},
new Person { FirstName="Michael", LastName="Schumacher"}
};
二.多維數組
多維數組用兩個或多個整數來索引。
在C#中聲明多維數組,需要在方括號中加上逗號。數組在初始化時應指定每一維的大小(也稱為階)。
int[,] twoDim = new int[3,3];
twoDim[0,0] = 1;
twoDim[0,1] = 2;
twoDim[0,2] = 3;
twoDim[1,0] = 4;
twoDim[1,1] = 5;
twoDim[1,2] = 6;
twoDim[2,0] = 7;
twoDim[2,1] = 8;
twoDim[2,2] = 9;
聲明數組之后,就不能修改其階數了。
也可以使用初始化器來初始化多維數組:
int[,] twoDim ={
{1,2,3},
{4,5,6},
{7,8,9}
};
使用數組初始化器時,必須初始化數組的每個元素,不能遺漏任何元素。
聲明一個三位數組:
int[,,] threeDim ={
{{1,2},{3,4}},
{{5,6},{7,8}},
{{9,10},{11,12}}
};
Console.WriteLine(threeDim[0,1,1]);
三.鋸齒數組
二維數組的大小對應於一個矩形,而鋸齒數組的大小設置比較靈活,在鋸齒數組中,每一行都可以有不同的大小。
在聲明鋸齒數組時,要依次放置左右括號。在初始化鋸齒數組時,只在第一對方括號中設置該數組包含的行數。定義各行中元素個數的第二個方括號設置為空,因為這類數組的每一行包含不同的元素個數。之后,為每一行指定行中的元素個數:
int[][] jagged = new int[3][];
jagged[0] = new int[2]{1,2};
jagged[1] = new int[4]{3,4,5,6};
jagged[2] = new int[3]{7,8};
迭代鋸齒數組中的所有元素的代碼可以放在嵌套的for循環中。在外層的for循環中迭代每一行,在內層的for循環中迭代一行中的每個元素:
for(int row = 0;row<jagged.Length;row++)
{
for(int element = 0;element<jagged[row].Length;element++)
{
Console.WriteLine("row:{0}, element:{1},value:{2}",row,element,jagged[row][element]);
}
}
四.Array類
用方括號聲明數組是C#中使用Array類的表示法。在后台使用C#語法,會創建一個派生自抽象基類Array的新類。這樣,就可以使用Array類為每個C#數組定義的方法和屬性了。
Array類實現的其它屬性有LongLength和Rank。如果數組包含的元素個數超出了整數的取值范圍,就可以使用LongLength屬性來獲得元素個數。使用Rank屬性可以獲得數組的維數。
1.創建數組
Array類是一個抽象類,所以不能使用構造函數來創建數組。但除了使用C#語法創建數組實例之外,還可以使用靜態方法CreateInstance()創建數組。如果事先不知道元素的類型,該靜態方法就很有用,因為類型可以作為Type對象傳遞給CreateInstance()方法。
CreateInstance()方法的第一個參數是元素的類型,第二個參數定義數組的大小。
可以使用SetValue()方法設置對應元素的值,用GetValue()方法讀取對應元素的值。
Array intArray1 = Array.CreateInstance(typeof(int), 5);
for (int i = 0; i < 5; i++)
{
intArray1.SetValue(33, i);
}
for (int i = 0; i < 5; i++)
{
Console.WriteLine(intArray1.GetValue(i));
}
還可以將已經創建的數組強制轉換稱聲明為int[]的數組:
int[] intArray2 = (int[])intArray1;
CreateInstance()方法有許多重載版本,可以創建多維數組和索引不基於0的數組。
//創建一個2X3的二維數組,第一維基於1,第二維基於10:
int[] lengths = { 2, 3 };
int[] lowerBounds = { 1, 10 };
Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds);
racers.SetValue(new Person { FirstName = "Alain", LastName = "Prost" }, index1: 1, index2: 10);
racers.SetValue(new Person
{
FirstName = "Emerson",
LastName = "Fittipaldi"
}, 1, 11);
racers.SetValue(new Person { FirstName = "Ayrton", LastName = "Senna" }, 1, 12);
racers.SetValue(new Person { FirstName = "Michael", LastName = "Schumacher" }, 2, 10);
racers.SetValue(new Person { FirstName = "Fernando", LastName = "Alonso" }, 2, 11);
racers.SetValue(new Person { FirstName = "Jenson", LastName = "Button" }, 2, 12);
Person[,] racers2 = (Person[,])racers;
Person first = racers2[1, 10];
Person last = racers2[2, 12];
2.復制數組
因為數組是引用類型,所以將一個數組變量賦予另一個數組變量,就會得到兩個引用同一數組的變量。
數組實現ICloneable接口。這個接口定義的Clone()方法會復制數組,創建數組的淺表副本。
如果數組的元素是值類型,Clone()方法會復制所有值:
int[] a1 = {1,2};
int[] a2 = (int[])a1.Clone();
如果數組包含引用類型,只復制引用。
除了使用Clone()方法之外,還可以使用Array.Copy()方法創建淺表副本。
Person[] beatles = {
new Person { FirstName="John", LastName="Lennon" },
new Person { FirstName="Paul", LastName="McCartney" }
};
Person[] beatlesClone = (Person[])beatles.Clone();
Person[] beatlesClone2 = new Person[2];
Array.Copy(beatlesClone,beatlesClone2,2);//注意與Clone的語法區別,Copy需要傳遞階數相同的已有數組。(還可以使用CopyTo()方法)
3.排序
Array類使用快速排序算法對數組中的元素進行排序。Sort()方法需要數組中的元素實現IComparable接口。因為簡單類型(如String,Int32)實現IComparable接口,所以可以對包含這些類型的元素排序。
string[] names = {
"Christina Aguilera",
"Shakira",
"Beyonce",
"Gwen Stefani"
};
Array.Sort(names);
foreach (string name in names)
{
Console.WriteLine(name);
}
如果對數組使用使用自定義類,就必須實現IComparable接口。這個接口只定義了一個方法CompareTo()方法,如果要比較的對象相等,該方法就返回0.如果該實例應排在參數對象的前面,該方法就返回小於i0de值。如果該實例應排在參數對象的后面,該方法就返回大於0的值。
public class Person : IComparable<Person>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return String.Format("{0} {1}",
FirstName, LastName);
}
public int CompareTo(Person other)
{
if (other == null) throw new ArgumentNullException("other");
int result = this.LastName.CompareTo(other.LastName);
if (result == 0)
{
result = this.FirstName.CompareTo(other.FirstName);
}
return result;
}
}
客戶端代碼:
Person[] persons = {
new Person { FirstName="Damon", LastName="Hill" },
new Person { FirstName="Niki", LastName="Lauda" },
new Person { FirstName="Ayrton", LastName="Senna" },
new Person { FirstName="Graham", LastName="Hill" }
};
Array.Sort(persons);
foreach (Person p in persons)
{
Console.WriteLine(p);
}
如果Person對象的排序方式與上述不同,或者不能修改在數組中用作元素的類,就可以實現IComparer接口或IComparer<T>接口。這兩個接口定義了方法Compare()方法。機型比較的類必須實現這兩個接口之一。
public enum PersonCompareType
{
FirstName,
LastName
}
//通過使用實現了IComparer<T> 泛型接口的PersonComparer類比較Person對象數組。
public class PersonComparer : IComparer<Person>
{
private PersonCompareType compareType;
public PersonComparer(PersonCompareType compareType)
{
this.compareType = compareType;
}
#region IComparer<Person> Members
public int Compare(Person x, Person y)
{
if (x == null) throw new ArgumentNullException("x");
if (y == null) throw new ArgumentNullException("y");
switch (compareType)
{
case PersonCompareType.FirstName:
return x.FirstName.CompareTo(y.FirstName);
case PersonCompareType.LastName:
return x.LastName.CompareTo(y.LastName);
default:
throw new ArgumentException(
"unexpected compare type");
}
}
#endregion
}
客戶端代碼:
Person[] persons = {
new Person { FirstName="Damon", LastName="Hill" },
new Person { FirstName="Niki", LastName="Lauda" },
new Person { FirstName="Ayrton", LastName="Senna" },
new Person { FirstName="Graham", LastName="Hill" }
};
Array.Sort(persons,
new PersonComparer(PersonCompareType.FirstName));
foreach (Person p in persons)
{
Console.WriteLine(p);
}
五.數組作為參數
數組可以作為參數傳遞給方法,也可以從方法中返回。
1.數組協變
數組支持協變。這表示數組可以聲明為基類,其派生類型的元素可以賦值於數組元素。
static void DisPlay(object[] o)
{
//..
}
可以給該方法傳遞一個Person[]。
數組協變只能用於引用類型,不能用於值類型。
2.ArraySegment<T>
結構ArraySegment<T>表示數組的一段。如果需要使用不同的方法處理某個大型數組的不同部分,那么可以把相應的數組部分復制到各個方法。
ArraySegment<T>結構包含了關於數組段的信息(偏移量和元素個數)。
static void Main()
{
int[] ar1 = { 1, 4, 5, 11, 13, 18 };
int[] ar2 = { 3, 4, 5, 18, 21, 27, 33 };
var segments = new ArraySegment<int>[2]
{
new ArraySegment<int>(ar1, 0, 3),
new ArraySegment<int>(ar2, 3, 3)
};
var sum = SumOfSegments(segments);
Console.WriteLine("sum of all segments: {0}", sum);
}
static int SumOfSegments(ArraySegment<int>[] segments)
{
int sum = 0;
foreach (var segment in segments)
{
for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
{
sum += segment.Array[i];
}
}
return sum;
}
數組段不復制原數組的元素,但原數組可以通過ArraySegment<T>訪問。如果數組段中的元素改變了,這些變化就會反映到原數組中。