下表總結了最常用的一些集合類:
1.1List<T>集合類
泛型List<T>類是最簡單的集合類。用法和數組差不多,可以使用標準數組語法(方括號和元素索引)來引用集合中的元素(但不能用這種語法在集合初始化之後添加新元素)。
List<T>類比數組靈活,避免了數組的以下限制。
(1)爲了改變數組大小,必須創建新數組,複製數組元素(如果新數組較小,甚至還復
制不完),然後更新對原始數組的引用,使其引用新數組。
(2)刪除一個數組元素,之後所有元素都必須上移-一位。即使這樣還是不好使,因爲最後一個元素會產生兩個拷貝。
(3)插入一個數組元素,必須使元素下移一位來騰出空位。但最後一個元素就丟失了!
List<T>集合類通過以下功能來避免這些限制。
(4)創建List<T>集合時無需指定容量,它能隨元素的增加而自動伸縮。這種動態行爲當然是有開銷的,如有必要可指定初始大小。超過該大小,List<T>集合 自動增大。
(5)可用 Remove方法從List<T>集合刪除指定元素。List<T>集合自動重新排序並關閉裂口。還可用RemoveAt方法刪除List<T>集合指定位置的項。
(6)可用 Add方法在List<T>集合尾部添加元素。只需提供要添加的元素,List<T>集合的大小會自動改變。
(7)可用Insert方法在List<T>集合中部插入元素。同樣地,List<T>集合的大小會自動改變。
(8)可調用Sort方法輕鬆對List<T>對象中的數據排序。
注意:和數組一樣,用foreach遍歷List<T>集合時,不能用循環變量修改集合內容。另外,在遍歷List<T>的foreach循環中不能調用Remove, Add或Insert 方法,否則會拋出InvalidoperationException。
下例展示瞭如何創建,處理和遍歷一個List<int>集合的內容。
using System;
using System.Collections ,Generic;
..
List<int> numbers = new List<int>();
//使用Add方法填充List<int>
foreach (int number in new int[12]{10, 9,8, 7,7,6,5,10,4,3,2, 1))
{
numbers.Add(number);
}
//在列表倒數第二個位置插入一個元素
//第一個參數是位置,第二個參數是要插入的值
numbers.Insert(numbers.Count-1, 99);
//刪除值是7的第一個元素(第4個元素,索引3)
numbers.Remove(7);
//刪除當前第7個元素,索引6 (10)
numtbers.RemoveAt(6);
//用for語句遍歷剩餘11個元素
Console.WriteLine("Iterating using a for statement:");
for (int i = 0; i < numbers.Count; 1++)
{
int number = numbers[1]; //注意,這裏使用了數組語法
Console.WriteLine(number);
}
//用foreach語句遍歷同樣的11個元素
Console .WriteLine("\nIterating using a foreach statement:");
foreach (int nunber in numbers)
{
Console .WriteLine(number);
}
代碼的輸出如下所示:
Iterating using a for statement:
10
9
8
7
6
5
4
3
2
99
1
Iterating using a foreach statement:
10
9
8
7
6
5
4
3
99
1
注意:List<T>集合和數組用不同的方式判斷元素數量。列表是用Count屬性,數組是用Length屬性。
1.2LinkedList<T>集合類
LinkedList<T>集合類實現了雙向鏈表。列表中每- -項除了容納數據項的值,還容納對下一項的引用(Next屬性)以及對上一項的引用(Previous屬性)。列表起始項的Previous屬性設爲null,最後一項的Next屬性設爲null。
和List<T>類不同,LinkedList<T>不 支持用數組語法插入和檢查元素。相反,要用AddFirst方法在列表開頭插入元素,下移原來的第一項並將它 的Previous屬性設爲對新項的用。或者用AddLast方法在列表尾插入元素,將原來最後一項的Next屬性設爲對新項的引用。還可使用AddBefore和AddAfter方法在指定項前後插入元素(要先獲取項)。
First屬性返回對LinkedList<T>集合第一- 項的引用,Last 屬性返回對最後-項的引用。遍歷鏈表可從任何一端開始,查詢Next或Previous引用,直到返回null爲止。還可使用foreach語句正向遍歷LinkedList<T>對象,抵達末尾會自動停止。
從LinkedList<T>集合中刪除項是使用Remove, RemoveFirst 和Removelast方法。
下例展示了一個LinkedList<T>集合。注意如何用for語句遍歷列表,它查詢Next(或Previous)屬性,直到屬性返回null引用(表明已抵達列表末尾)。
using System;
using System.Collections . Generic;
...
LinkedList<int> numbers = new LinkedList<int>();
//使用AddFirst方法填充列表
foreach (int number in new int[] { 10,8,6,4,2 })
{
numbers .AddFirst(number);
}
//用for語句遍歷
Console.WriteLine( "Iterating using a for statement:");
for (LinkedListNodecint> node = numbers.First; node l= null; node = node.Next)
{
int number = node . Value;
Console.WriteLine(number);
}
//用foreach語句遍歷
Console.Writeline("\nIterating using a foreach statement:");
foreach (int mumber in numbers)
{
Console.WriteLine(number);
}
//反向遍歷(只能用for, foreach 只能正向遍歷)
Console.WriteLine("\nIterating list in reverse order:");
for (LinkedListNode<int> node = numbers.Last; node 1= null; node = node.Prevlous)
{
int number = node.Value;
Console.WriteLine(number);
}
代碼的輸出如下所示:
Iterating using a for statement:
2
4
6
8
10
Iterating using a foreach statement:
2
4
6
8
10
Iterating list in reverse order:
10
8
6
4
2
1.3Queue<T>集合類
Queue<T>類實現了先入先出隊列。元素在隊尾插入(入隊或Enqueue), 從隊頭移除(出
隊或Dequeue)。
下例展示了一個Queue<int>集合及其常見操作:
using System;
using System.Collections ,Generic;
Queuecint> numbers = new Queuecint>();
//填充隊列
Console.WriteLine("Populating the queue:");
foreach (int numiber in new int[4]{9, 3, 7, 2})
{
numbers. Enqueue(number);
Console.WriteLine($" (number} has joined the queue");
}
//遍歷隊列
Console.WriteLine("\nThe queue contains the followding items:");
foreach (int number in numbers)
{
Console.WriteL ine(number);
)
//清空隊列
Console.Writel ine("\nDraining the queue:");
while (numbers .Count>0)
{
int number = numbers .Dequeue();
Console.Writel ine($"(nunber} has left the queue");
}
上述代碼的輸出如下:
Populating the queue:
9 has joined the queue
3 has joined the queue
7 has joined the queue
2 has joined the queue
The queue contains the following items:
9
3
7
2
Draining the queue:
9 has left the queue
3 has 1eft the queue
7 has left the queue
2 has 1eft the queue
1.4Stacke<T>集合類
Stack<T>類實現了後入先出的棧。元素在項部入棧(push),從頂部出棧(pop)。通常可以將棧想象成一疊盤子:新盤子疊加到項部,同樣從項部取走盤子。換言之,最後一個入棧的總是第一個被取走的。下面是一個例子(注意foreach循環列出項的順序):
using System;
using System.Collections.Generic;
…
Stack<int> numbers = new Stack<int>();
//填充棧-入棧
Console.WriteLine("Pushing itens onto the stack:");
foreach (int number in new int[4]{9, 3, 7, 2))
{
members. Push(number);
Console.WiriteLine($"(number} has been pushed on the stack");
)
//遍歷棧
Console .WriteLine("\nThe stack now contains:");
foreach (int number in nunmbers)
{
console.WriteLine(number);
}
//清空棧
Console.WriteLine("\nPopping items from the stack:");
while (numbers.Count > 0)
{
int number = numbers .Pop();
Console.WriteLine($"{number} has been popped off the stack");
}
下面是程序的輸出:
Pushing items onto the stack:
9 has been pushed on the stack
3 has been pushed on the stack
7 has been pushed on the stack
2 has been pushed on the stack
The stack now contains :
2
7
3
9
Popping items from the stack:
2 has been popped off the stack
7 has been popped off the stack
3 has been popped off the stack
9 has been popped off the stack
1.5Dictionary<TKey, TValue>集合類
數組和List<T> 類型提供了將整數索引映射到元素的方式。在方括號中指定整數索引(例如[4])來獲取索引4的元素(實際是第5個元素)。但有時需要從非int類型(比如string,double或Time)映射。其他語言一般把這稱 爲關聯數組。C#的Dictionary<TKey, TValue>類在內部維護兩個數組來實現該功能。-一個存儲要從其映射的鍵,另一個存儲映射到的值。分別稱爲鍵數組和值數組。在Dictionary<TKey, TValue>集 閤中插入鍵/值對時,將自動記錄哪個鍵和哪個值關聯,允許開發人員快速、簡單地獲取具有指定鍵的值。Dictionary<TKey, TValue>類的設計產生了-些重要後果。
(1)Dictionary<TKey, TValue>集合 不能包含重複的鍵。調用Add方法添加鍵數組中已有的鍵將拋出異常。但是,如果使用方括號記號法來添加鍵/值對(參見後面的例子),就不用擔心異常一即 使之前已添加了相同的鍵。如果鍵已經存在,其值就會被新值覆蓋。可用ContainKey 方法測試Dictionary<TKey, TValue>集合是否已包含特定的鍵。
(2)Dictionary<TKey, TValue>集合內部採用一種稀疏數據結構,在有大量內存可用時才最高效。隨着更多元素的插入,Dictionary<TKey, TValue>集合可能快速消耗大量內存。
(3)使用foreach 語句遍歷Dictionary<TKey, TValue>集 合返回的是一個KeyValuePair<TKey, TValue>.。這是一個結構,包含的是數據項的鍵和值元素的拷貝,可通過Key和Value屬性訪問每個元素。元素是隻讀的,不能用它們修改Dictionary<TKey, TValue> 集合中的數據。
下例將家庭成員年齡和姓名關聯並打印信息。
using System;
using System. collections .Generic;
…
Dictionary<string, int> ages = new Dictionary<string, int>();
//填充字典
ages .Add("John", 51); //使用Add方法
ages .Add("Diana", 50);
agesI["anes"] = 23; /使用數組語法
ages["Francesca"] = 21;
//用foreach語句遍歷字典
//迭代器生成的是個KeyValuePair項
Console.WriteLine("The Dictionary contains:");
foreach (KeyWaluePair<string, int> element in ages)
{
string name = element .Key;
int age = element .Value;
Console .WriteLine($"Name: {name}, Age: {age}");
}
程序輸出如下所示:
The Dictionary contains:
Name: John, Age: 51
Name: Diana, Age: 50
Name: James, Age: 23
Name: Francesca, Age: 21
1.6SortedList<Tkey,TValue>集合類
SortedList<TKey, TValue>類 與Dictionary<TKey, TValue>類很相似, 都允許將鍵和值關聯。主要區別是,前者的鍵數組總是排好序的(不然也不會叫SortedList 了)。在SortedList<TKey, TValue>對 象中插入數據花的時間比Dictionary<TKey,TValue>對象長,但獲取數據會快-一些(至少一樣快), 而且SortedList<TKey, TValue>類消耗內存較少。
在SortedList<Tkey, TValue>集合中插入- 一個鍵/值對時,鍵會插入鍵數組的正確索引位置,目的是確保鍵數組始終處於排好序的狀態。然後,值會插入值數組的相同索引位
置。SortedList<TKey, TValue>類自動保證鍵和值同步,即使是在添加和刪除了元素之後。
這意味着可按任意順序將鍵/值對插入-一個SortedList<TKey, TValue>, 它們總是按照鍵的值來排序。
與Dictionary<TKey, TValue>類相似, SortedList<TKey, TValue>集合不能包含重複鍵。用foreach語句遍歷SortedList<TKey, TValue>集合返回的是KeyValuePair<TKey,TValue>項,只是這些KeyValuePair<TKey, TValue>對 象已按Key屬性排好序。
下例仍然將家庭成員的年齡和姓名關聯並打印結果。但這一次使用的是有序列表而不是字典。
using System;
using System.Collections .Generic;
…
Sortedlist<string, int> ages = new SortedList<string, int>();
//填充有序列表
ages.Ad("John", 51); //使用Add方法
ages .Add("Diana", 50);
ages["James"] = 23;//使用數組語法
ages["Francesca"] = 21;
//用foreach語句遍歷有序列表
//迭代器生成的是一個KeyWaluePair項
Console.WriteLine("The SortedList contains:");
foreach (KeyaluePair<string, int> element in ages)
{
string name = element .Key;
int age = element.Value;
Console.WriteLine($"Name: {name), Age: {age}");
}
結果按家庭成員姓名(鍵)的字母順序進行排序(D-F-J-J);
The SortedList contains:
Name: Diana, Age: 50
Name: Francesca, Age: 21
Name: James, Age: 2
Name: John, Age: 51
1.7HashSet<T>集合類
HashSet<T>類專爲集合操作優化,包括判斷數據項是否集合成員和生成並集/交集等。數據項用Add方法插入HashSet<T>集合,用Remove方法刪除。但是,HashSet<T>類真正強大的是它的IntersectWith, UnionWith 和Exceptwith 方法。這些方法修改HashSet<T>集合來生成與另一個HashSet<T>相交、合併或者不包含其數據項的新集合。這些操作是破壞性的,因爲會用新集合覆蓋原始HashSet<T>對象的內容。另外,還可以使用IsSubsetOf, IsSupersetof, IsProperSubsetof 和IsProperSupersetOf方法判斷一個HashSet<T>集合的數據是否另一個HashSet<T>集 合的超集或子集。這些方法返回Boolean值,是非破壞性的。
HashSet<T>集合內部作爲哈希表實現,可實現數據項的快速查找。但是,一個大的HashSet<T>集合可能需要消耗大量內存。
下例展示如何填充HashSet<T>集合並運用Intersectwith方法找出兩個集合都有的數據。
using System;
using System.Collections .Generic;
…
HastSet<string> employees = new Haset<string>(new string[] ("Fred',"Bert","Harry" ,"John"});
HashSet<string> castoners = new HshSet<string>(new string[] ({"John","Sid","Harry", "Diana"));
employees Add("James");
customers Add( "Francesca");
Console.WriteLine( "Employees:");
foreach (string nane in employees)
{
Console ,WriteLine(name);
}
Console.Writel ine("\nCustomers:");
foreach (string name in customers)
{
Console.WriteLine(nane);
}
Console.Writel Ine("\nCustomers who are also employees:"); //既是客戶又是員工的人
customers.。Intersectwith( ermployees);
foreach (string name in customers)
{
Console .WriteLine(nane);
}
代碼的輸出如下所示:
Employees:
Fred
Bert
Harry
John
James
Customers :
John
sid
Harry
Diana
Francesca
Customers who are also employees:
John
Harry
注意:System.Collections .Generic命名空間還包含SortedSet<T>集合類型。工作方式和HashSet<T>相似。主要區別是數據保持有序。Sortedset<T> 和HashSet<T>類可以互操作。例如,可以獲取SortedSet<T>集合和HashSet<T>集合的並集。
2.使用集合初始化器
前面的例子展示瞭如何使用每種集合最合適的方法來添加元素。例如,List<T>使用Add, Queue<T>使用 Enqueue,而Stack<T>使用Push.一些集合類型還允許在聲明時使用和數組相似的語法來初始化。例如,以下語句創建並初始化名爲numbers的List<int>對象,這樣寫就不需要反覆調用Add方法了:
List<int> numbers =new List<int>(){10, 9, 8, 7, 7,6,5, 10, 4, 3, 2, 1});
C#編譯器內部會將初始化轉換成--系列Add方法調用。換言之,只有支持Add方法的集合才能這樣寫(Stack<T>和Queue<T>就不行)。
對於獲取鍵/值對的複雜集合(例如Dictionary<TKey, TValue>), 可用索引器語法爲每個鍵指定值,例如:
Dictionary<string, int> ages = new Dictionary<string, int>()
{
["John"] = 51,
["Diana"]=50,
["James"] = 23,
["Francesca"] = 21
};
如果願意,還可在集合初始化列表中將每個鍵/值對指定爲匿名類型,如下所示:
Dictionary<string, int> ages = new Dictlonary<string, int>()
{
{"John", 51]},
{"Dlana", 50},
{"James", 23},
{"Francesca", 21}
};
每一對的第一一項是鍵, 第二項是值。爲增強代碼可讀性,建議初始化字典類型時儘量使用索引器語法。
3.Find方法、謂詞和Lambda表達式
對於List<T>和LinkedList<T>等支持無鍵隨機訪問的集合,它們無法通過數組語法來查找項,所以專門提供了Find方法。Find 方法的實參是代表搜索條件的謂詞。謂詞就是一個方法,它檢查集合的每一項,返回Boolean值指出該項是否匹配。
Find方法返回的是發現的第一個匹配項。List<T>和LinkedList<T>類還支持其他方法,例如FindLast返回最後一個匹配項。List<T>類還專門有一個FindAll 方法,返回所有匹配項的一個List<T>集合。
謂詞最好用Lambda表達式指定。簡單地說,Lambda 表達式是能返回方法的表達式。
一般的方法由4部分組成:返回類型、方法名、參數列表和方法主體。
Lambda表達式只由兩部分組成:參數列表和方法主體。
在Find方法的情況下,謂詞依次處理集合中的每一項: 謂詞的主體必須檢查項,根據是否匹配搜索條件返回true或false.以下加粗的語句在一一個 List<Person>上調用Find方法(Person是結構),返回ID屬性爲3的第一項。
struct Person
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
...
//創建並填充personnel列表
List<Person) personnel . new List<Person>()
{
new Person() { ID= 1, Name="John", Age = 51 },
new Person() { ID = 2, Name="Sid", Age =28 },
new Person() { ID=3, Name="Fred", Age = 34 },
new Person() { ID=4, Name="Paul", Age=22 },
};
//查找ID爲3的第一個列表成員
Person match = personnel.Find((Person p) =>{ return p.ID== 3; };
Console.WriteLine($"ID: {match. ID}\nName: {match.Name}\nAge: {match.Age}");
上述代碼的輸出如下:
ID: 3
Name: Fred
Age: 34
調用Find方法時,實參(Person p) => { return p.ID== 3; }就是實際“幹活兒”的Lambda表達式,它包含以下語法元素。
(1)圓括號中的參數列表。和普通方法一樣,即使Lambda表達式代表的方法不獲取
任何參數,也要提供一對空白圓括號。 對於Find 方法,謂詞要針對集合中的每
一項運行, 該項作爲參數傳給Lambda表達式。
(2)=>操作符, 它向C#編譯器指出這是一一個Lambda表達式。
(3)Lambda 表達式主體(方法主體)。本例的主體很簡單,只有一個語句,返回Boolean
值來指出參數所指定的項是否符合搜索條件。然而,Lambda表達式完全可以包含多個語句,而且可以採用你覺得最易讀的方式來排版。只是要記住,和普通方法一樣,每個語句都要以分號結束。
Lambda表達式的一些特點:
(1)如Lambda 表達式要獲取參數,就在=>操作符左側的圓括號內指定。 可省略參數類型,C#編譯器能根據Lambda表達式的上下文進行推斷。如希望Lambda表達式永久(而不是局部)更改參數值,可用“傳引用”方式傳遞參數(使用ref關鍵字),但不推薦這樣做。
(2)Lambda表達式可返回值,但返回類型必須與對應的委託的類型匹配。
(3)Lambda 表達式主體可以是簡單表達式,也可以是C#代碼塊(代碼塊可包含多個語句,方法調用、變量定義等等)。
(4)Lambda 表達式方法中定義的變量會在方法結束時離開作用城(失效)。
(5)Lambda表達式可訪問和修改Lambda 表達式外部的所有變量,只要那些變量在Lambda表達式定義時,和Lambda表達式處在相同作用城中。一定要非常留意這個特點!
4.比較數組和集合
(1)數組實例具 有固定大小,不能增大或縮小。集合則可根據需要動態改變大小。
(2)數組可以多維, 集合則是線性。但集合中的項可以是集合自身,所以可用集合的
集合來模擬多維數組。
(3)數組中的項通過索引來存儲和獲取。並非所有集合都支持這種語法。例如,要用Add或Insert方法在List<T>集合中存儲項,用Find方法獲取項。
(4)許多集合 類都提供了ToArray方法,能創建數組並用集合中的項來填充。複製到數組的項不從集合中刪除。另外,這些集合還提供了直接從數組填充集合的構造器。
參考書籍:《Visual C#從入門到精通》