c#中Array,ArrayList 與List<T>的區別、共性與轉換

sogeisetsu發表於2021-12-18

本文內容來自我寫的開源電子書《WoW C#》,現在正在編寫中,可以去WOW-Csharp/學習路徑總結.md at master · sogeisetsu/WOW-Csharp (github.com)來檢視編寫進度。預計2021年年底會完成編寫,2022年2月之前會完成所有的校對和轉制電子書工作,爭取能夠在2022年將此書上架亞馬遜。編寫此書的目的是因為目前.NET市場相對低迷,很多優秀的書都是基於.NET framework框架編寫的,與現在的.NET 6相差太大,正規的.NET 5學習教程現在幾乎只有MSDN,可是MSDN雖然準確優美但是太過瑣碎,沒有過閱讀開發文件的同學容易一頭霧水,於是,我就編寫了基於.NET 5的《WoW C#》。本人水平有限,歡迎大家去本書的開源倉庫sogeisetsu/WOW-Csharp關注、批評、建議和指導。

Array,ArrayList and List<T>

Array、ArrayList和List都是從IList派生出來的,它們都實現了IEnumerable介面

從某種意義上來說,ArrayList和List屬於集合的範疇,因為他們都來自程式集System.Collections,但是因為它們都是儲存了多個變數的資料結構,並且都不是類似鍵值對的組合,並且沒有先進先出或者先進後出的機制,故而稱為陣列。

我們一般稱呼Array,ArrayList and List<T>為陣列。

Array

Array必須在定義且不初始化賦值的時候(不初始化的情況下宣告陣列變數除外)必須定義陣列最外側的長度。比如:

int[] vs = new int[10];
int[,] duoWei = new int[3, 4];
int[][] jiaoCuo = new int[3][]; // 該陣列是由三個一維陣列組成的

一維陣列

定義

用類似於這種方式定義一個陣列

int[] array = new int[5];

初始化賦值

用類似於這種方式初始化

int[] array1 = new int[] { 1, 3, 5, 7, 9 };

也可以進行隱式初始化

int[] array2 = { 1, 3, 5, 7, 9 };
string[] weekDays2 = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

用類似於下面這種方式先宣告,再賦值

int[] array3;
array3 = new int[] { 1, 3, 5, 7, 9 };   // OK
//array3 = {1, 3, 5, 7, 9};   // Error

多維陣列

陣列可具有多個維度。多維陣列的每個元素是宣告時的陣列所屬型別的元素。比如說int[,]的每個元素都是int型別而不是int[]型別。換種說法就是多維陣列不能算做“陣列組成的陣列”

定義

用類似下面這種方式宣告一個二維陣列的長度

int[,] array = new int[4, 2];

初始化賦值

用類似於下面的方式初始化多維陣列:

// Two-dimensional array.
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// The same array with dimensions specified.
int[,] array2Da = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// A similar array with string elements.
string[,] array2Db = new string[3, 2] { { "one", "two" }, { "three", "four" },
                                        { "five", "six" } };

// Three-dimensional array.
int[,,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } },
                                 { { 7, 8, 9 }, { 10, 11, 12 } } };

還可在不指定級別的情況下初始化陣列

int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };

不初始化的情況下宣告陣列變數:

int[,] array5;
array5 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };   // OK
//array5 = {{1,2}, {3,4}, {5,6}, {7,8}};   // Error

元素賦值和獲取元素

可以用類似於array[1,2]的方式來獲取陣列的值和為陣列賦值。

GetLength(0)可以獲取最外圍陣列的長度,GetLength(1)可以獲得第二層陣列的長度。以此類推。為一個二維陣列duoWei迴圈賦值的方式如下:

Console.WriteLine("二維陣列賦值");
for (int i = 0; i < duoWei.GetLength(0); i++)
{
    for (int j = 0; j < duoWei.GetLength(1); j++)
    {
        duoWei[i, j] = i + j;
    }
}

如何獲取二維陣列中的元素個數呢?

int[,] array = new int[,] {{1,2,3},{4,5,6},{7,8,9}};//定義一個3行3列的二維陣列
int row = array.Rank;//獲取維數,這裡指行數
int col = array.GetLength(1);//獲取指定維度中的元素個數,這裡也就是列數了。(0是第一維,1表示的是第二維)
int col = array.GetUpperBound(0)+1;//獲取指定維度的索引上限,在加上一個1就是總數,這裡表示二維陣列的行數
int num = array.Length;//獲取整個二維陣列的長度,即所有元的個數

來源:C#中如何獲取一個二維陣列的兩維長度,即行數和列數?以及多維陣列各個維度的長度? - jack_Meng - 部落格園 (cnblogs.com)

交錯陣列

交錯陣列是一個陣列,其元素是陣列,大小可能不同。 交錯陣列有時稱為“陣列的陣列”。

交錯陣列不初始化就宣告的方式如下:

int[][] ccf;
ccf = new int[3][];

交錯陣列類似於python的多維陣列,比較符合人類的直覺,一個交錯陣列裡麵包含了多個陣列。

定義

可以採用類似下面的方式來宣告一個交錯陣列:

// 定義多維陣列要求每個維度的長度都相同 下面定義交錯陣列
int[][] jiaoCuo = new int[3][]; // 該陣列是由三個一維陣列組成的

上面宣告的陣列是具有三個元素的一維陣列,其中每個元素都是一維整數陣列。

可使用初始化表示式通過值來填充陣列元素,這種情況下不需要陣列大小。 例如:

jaggedArray[0] = new int[] { 1, 3, 5, 7, 9 };
jaggedArray[1] = new int[] { 0, 2, 4, 6 };
jaggedArray[2] = new int[] { 11, 22 };

初始化賦值

可在宣告陣列時將其初始化,如:

int[][] jaggedArray2 = new int[][]
{
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};

獲取元素和單個賦值

可以用類似於jiaoCuo[1][1]來獲取單個元素的值,也可以用類似於jiaoCuo[1][1] = 2;來為單個元素賦值。

可以採取類似於下面的方式來進行迴圈賦值:

Console.WriteLine("交錯陣列迴圈賦值");
// 先宣告交錯陣列中每一個陣列的長度
for (int i = 0; i < 3; i++)
{
    jiaoCuo[i] = new int[i + 1];
}
// 然後對交錯陣列中的每一個元素賦值
for (int i = 0; i < jiaoCuo.Length; i++)
{
    Console.WriteLine($"交錯陣列的第{i + 1}層");
    for (int j = 0; j < jiaoCuo[i].Length; j++)
    {
        jiaoCuo[i][j] = i + j;
        Console.WriteLine(jiaoCuo[i][j]);
    }
}

方法和屬性

像陣列這種儲存多個變數的資料結構,最重要的就是增查刪改、獲取長度和資料型別轉換Array因為陣列的特性,長度不可改變,所以增查刪改只能有查和改。

Array型別用用類似於下面的方式進行改操作:

vs[0] = 12; //一維陣列
duoWei[1, 2] = 3; //多維陣列
jiaoCuo[1][1] = 2; //交錯陣列

Array型別用類似於下面的方式進行查操作:

int[] vs = new int[10];
vs[0] = 12;
Console.WriteLine(Array.IndexOf(vs, 12)); //0
Console.WriteLine(vs.Contains(12)); // True

獲取長度

可以用類似於下面這種方式來獲取:

Console.WriteLine(vs.Length);
Console.WriteLine(vs.Count());

交錯陣列的Length是獲取所包含陣列的個數,多維陣列的Length是獲取陣列的元素的總個數,多維陣列GetLength(0)可以獲取最外圍陣列的長度,GetLength(1)可以獲得第二層陣列的長度。以此類推。

Array.ConvertAll() 資料型別轉換

可以用Array.ConvertAll<TInput,TOutput>(TInput[], Converter<TInput,TOutput>) 來進行陣列型別的轉換。

引數如下:

  • array

    TInput[]

要轉換為目標型別的從零開始的一維 Array

用於將每個元素從一種型別轉換為另一種型別的 Converter


來源:[Array.ConvertAll(TInput], Converter) 方法 (System) | Microsoft Docs

demo如下:

double[] vs3 = Array.ConvertAll(vs, item => (double)item);

切片

預設狀態下只能對一維陣列進行切片,或者通過交錯陣列獲取的一維陣列也可以進行切片。

切片的方式類似於vs[1..5],表示vs陣列從1到5,左閉右開。^1表示-1,即最後一個元素。[^3..^1]表示倒數第三個元素到倒數第一個元素,左閉右開。

獲取單個元素和賦值

可以採用下面的方式來獲取單個元素和為單個元素單獨賦值:

// 一維陣列
Console.WriteLine(vs[1]);
vs[1] = 2;
// 多維陣列
Console.WriteLine(duoWei[1, 2]);
duoWei[1, 2] = 3;
// 交錯陣列
Console.WriteLine(jiaoCuo[1][0]);
jiaoCuo[1][0] = 0;

Array.ForEach 迴圈

System.Array裡面也有ForEach方法,這是用於Array的。

demo:

Array.ForEach(vs, item => Console.WriteLine(item));

ArrayList

定義

用類似於下面的三種方式中的任意一種來宣告ArrayList:

ArrayList() 初始化 ArrayList 類的新例項,該例項為空並且具有預設初始容量。
ArrayList(ICollection) 初始化 ArrayList 類的新例項,該類包含從指定集合複製的元素,並具有與複製的元素數相同的初始容量。
ArrayList(Int32) 初始化 ArrayList 類的新例項,該例項為空並且具有指定的初始容量。

可以將Arraylist看作是一種長度可以自由變換,可以包含不同資料型別元素的陣列。

初始化賦值

可以採用類似於下面的方式來初始化賦值:

ArrayList arrayList1 = new ArrayList() { 12, 334, 3, true };

迴圈

迴圈可以用for和foreach。

foreach (var item in arrayList)
{
    Console.WriteLine(item);
}

方法和屬性

list<T>類似,但是沒有ConvertAll方法。ArrayList本身沒有ForEach方法,但是也可以用傳統的foreach方法(就像前面提到的ArrayList的迴圈那樣)。

具體的方法和屬性請檢視List部分的方法和屬性

List<T>

定義

用類似於下面的三種方式中的任意一種來宣告List<T>

List() 初始化 List 類的新例項,該例項為空並且具有預設初始容量。
List(IEnumerable) 初始化 List 類的新例項,該例項包含從指定集合複製的元素並且具有足夠的容量來容納所複製的元素。
List(Int32) 初始化 List 類的新例項,該例項為空並且具有指定的初始容量。

初始化

用類似於下面的方式在宣告時初始化:

List<string> listA = new List<string>() { "hello", " ", "wrold" };

迴圈

List<T>有一個名稱為ForEach的方法:

public void ForEach (Action<T> action);

該方法的本質是要對 List 的每個元素執行的 Action 委託。Action 的引數即為List<T>在迴圈過程中的每個元素。

demo如下:

// 宣告
List<string> listA = new List<string>() { "hello", " ", "wrold" };
// 迴圈
var i = 0;
listA.ForEach(item =>
              {
                  Console.WriteLine($"第{i + 1}個");
                  Console.WriteLine(item);
                  i++;
              });

方法和屬性

從獲取長度、增查刪改、資料型別轉換、切片和迴圈來解析。其中除了資料型別轉換和List<T>型別本身就擁有的ForEach方法外,都適用於ArrayList。

先宣告一個List<string>作為演示的基礎:

List<string> listA = new List<string>() { "hello", " ", "wrold" };

屬性 長度

Count屬性可以獲取長度

Console.WriteLine(listA.Count);

屬性 取值

Console.WriteLine(listA[0]);

即增加元素,可以用Add方法:

listA.Add("12");

IndexOf獲取所在位置,Contains獲取是否包含。

Console.WriteLine(listA.IndexOf("12"));
Console.WriteLine(listA.Contains("12"));

Remove根據資料刪除,RemoveAt根據位置刪除。

listA.Remove("12");
listA.RemoveAt(1);

可以用類似於listA[1] = "改變";的方式來修改元素內容。

切片

可以用GetRange(int index, int count)來進行切片操作,第一個引數是切片開始的位置,第二個引數是切片的數量,即從index開始往後數幾個數。

Console.WriteLine(listA.GetRange(1, 1).Count);

迴圈

List<T>有一個名稱為ForEach的方法,該方法的本質是要對 List 的每個元素執行的 Action 委託。Action 的引數即為List<T>在迴圈過程中的每個元素。

demo如下:

// 宣告
List<string> listA = new List<string>() { "hello", " ", "wrold" };
// 迴圈
var i = 0;
listA.ForEach(item =>
              {
                  Console.WriteLine($"第{i + 1}個");
                  Console.WriteLine(item);
                  i++;
              });

資料型別轉換

可以用ConvertAll來對陣列的資料型別進行轉換,這是List<T>自帶的方法。System.Array裡面也有ConvertAll方法,這是用於Array的。

List<object> listObject = listA.ConvertAll(s => (object)s);

區別

成員單一型別 長度可變 切片友好 方法豐富 增查刪改 ConvertAll
一維陣列 查、改
多維陣列 查、改
交錯陣列 查、改
ArrayList 增查刪改
List<T> 增查刪改

Array最大的好處就是切片友好,可以使用類似於[1..3]的方式切片,這是比GetRange更加直觀的切片方式。List<T>型別可以通過ToArray的方法來轉變成Array。

Array,ArrayList and List<T>之間的轉換

關於這一部分的demo程式碼詳情可從Array,ArrayList and List之間的轉換 · sogeisetsu/Solution1@88f27d6 (github.com)獲得。

先分別宣告這三種資料型別。

// 宣告陣列
int[] a = new int[] { 1,3,4,5,656,-1 };
// 宣告多維陣列
int[,] aD = new int[,] { { 1, 2 }, { 3, 4 } };
// 宣告交錯陣列
int[][] aJ = new int[][] {
    new int[]{ 1,2,3},
    new int[]{ 1}
};
// 宣告ArrayList
ArrayList b = new ArrayList() { 1, 2, 344, "233", true };
// 宣告List<T>
List<int> c = new List<int>();

Array轉ArrayList

// 陣列轉ArrayList
ArrayList aToArrayList = new ArrayList(a);

Array轉List<T>

List<int> aToList = new List<int>(a);
List<int> aToLista = a.ToList();

List<T>轉Array

int[] cToList = c.ToArray();

List<T>轉ArrayList

ArrayList cToArrayList = new ArrayList(c);

ArrayList轉Array

在轉換的過程中,會丟失資料型別的準確度,簡單來說就是轉換成的Array會變成object

// ArrayList轉Array
object[] bToArray = b.ToArray();

這種轉換的意義不大,如果轉換完之後再強行用Array.ConvertAll方法來進行資料型別的轉換,很有可能會出現諸如Unable to cast object of type 'System.String' to type 'System.Int32'.的錯誤,這是因為ArrayList本身成員就可以不是單一型別。

陣列的列印

Array的列印

對於Array的列印,我找到了四種方式,如下:

  • 呼叫Array.ForEach

    Array.ForEach(a, item => Console.WriteLine(item));
    
  • 傳統forEach

    foreach (var item in a)
    {
    Console.WriteLine(item);
    }
    
  • 傳統for

    for (int i = 0; i < a.Count(); i++)
    {
    Console.WriteLine(a[i]);
    }
    
  • string.Join

    Console.WriteLine(string.Join("\t", a));
    

ArrayList的列印

ArrayList的列印我知道的就只有傳統的for和foreach兩種方式。

List<T>的列印

List<T>的列印除了傳統的for和foreach兩種方式之外,還有List<T>本身自帶的foreach:

var i = 0;
listA.ForEach(item =>
              {
                  Console.WriteLine($"第{i + 1}個");
                  Console.WriteLine(item);
                  i++;
              });

請注意:ArrayList和List<T>均沒有string.Join和呼叫Array.ForEach兩種方式來列印陣列。

LICENSE

已將所有引用其他文章之內容清楚明白地標註,其他部分皆為作者勞動成果。對作者勞動成果做以下宣告:

copyright © 2021 蘇月晟,版權所有。

知識共享許可協議
作品蘇月晟採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。

相關文章