集合(collection)提供了一種結構化組織任意對象的方式,從廣義的概念上講,數組、枚舉和結構等組合都是集合的一種表現,其內部的元素組織方式也都與集合的定義非常類似。但在C#中,集合這一專有名詞特指System.Collections命名空間下的各種子類,數組、枚舉和結構都不是System.Co
集合(collection)提供了一種結構化組織任意對象的方式,從廣義的概念上講,數組、枚舉和結構等組合都是集合的一種表現,其內部的元素組織方式也都與集合的定義非常類似。但在C#中,集合這一專有名詞特指System.Collections命名空間下的各種子類,數組、枚舉和結構都不是System.Collections命名空間的成員。NET類庫提供了豐富的集合數據類型,其種類之繁多甚至使許多人看得眼都花了,這些集合對象都具有各自的專用場合。不管怎麼說,更多的選擇也就意味著更高的靈活性,但同時也意味著更高的複雜性。因此,對集合各個類型的用途和使用條件具有適度的瞭解是完全必要的。
.NET集合定義
從.NET 的角度看,所謂的集合可以定義為一種對象,這種對象實現一個或者多個System.Collections.ICollection、 System.Collections.IDictionary和System.Collections.IList介面。這一定義把 System.Collections名稱空間中的“內置”集合劃分成了三種類別:
* 有序集合:僅僅實現ICollection介面的集合,在通常情況下,其數據項目的插入順序控制著從集合中取出對象的的順序。 System.Collections.Stack和 System.Collections.Queue類都是ICollection集合的典型例子。
* 索引集合:實現Ilist的集合,其內容能經由從零開始的數字檢索取出,就象數組一樣。System.Collections.ArrayList對象是索引集合的一個例子。
* 鍵式集合:實現 IDictionary 介面的集合,其中包含了能被某些類型的鍵值檢索的項目。IDictionary集合的內容通常按鍵值方式存儲,可以用枚舉的方式排序檢索。 System.Collections.HashTable類實現了IDictionary 介面。
正如你看到的那樣,給定集合的功能在很大程度上受到特定介面或其實現介面的控制。如果你對面向對象編程缺乏瞭解,那麼你可能對上面說的這些話感到難以理解。不過你至少應該知道,以介面這種方式構造對象的功能不但造就了具有整套類似方法的對象族,而且還能讓這些對象在必要的情況下可以當作同類,以OOP (面向對象編程)的術語來說,這就是大名鼎鼎的多態性技術。
System.Collections概述:
System.Collections 名稱空間包含了在你的應用程式中可以用到的6種內建通用集合。另一些更為專業化的集合則歸屬於 System.Collections.Specialized,在某些情況下你會發現這些專用集合也是非常有用的。加上一些異常(exception)類,這些專業化集合在功能上和內建集合是類似的。
System.Collections.Generic概述:
System.Collections.Generic 命名空間包含定義泛型集合的介面和類,泛型集合允許用戶創建強類型集合,它能提供比非泛型強類型集合更好的類型安全性和性能。
System.Collections命名空間下常用的集合類有:
- ArrayList
- Queue
- Stack
- BitArray
- Hashtable
- SortedList
System.Collections.Generic命名空間下常用的集合類有:
- List<T>
- Queue<T>
- Stack<T>
- LinkedList<T>
- HashSet<T>
- Dictionary<TKey, TValue>
- SortedDictionary<TKey, TValue>
- SortedList<TKey, TValue>
- Lookup<TKey, TElement>
System.Collections命名空間中的介面
System.Collections命名空間中的幾個介面提供了基本的集合功能
- IEnumerable:公開枚舉數,該枚舉數支持在非泛型集合上進行簡單迭代。簡單的說就是實現 IEnumerable介面後可以支持用Microsoft Visual Basic 的 foreach 語義來進行迭代集合中的項。它只有一個方法 GetEnumerator(),該方法可以返回一個IEnumerator枚舉數,通過它可以遍歷集合。基本上所有的集合類都實現了這個方法。
- ICollection:定義所有非泛型集合的大小、枚舉數和同步方法。ICollection 介面是 System.Collections 命名空間中類的基介面,所有集合類都實現了這個介面。ICollection 介面繼承於IEnumerable,擴展了IEnumerable。 IDictionary 和 IList 則是擴展 ICollection 的更為專用的介面。如果 IDictionary 介面和 IList 介面都不能滿足所需集合的要求,則從 ICollection 介面派生新集合類以提高靈活性。
- IList:表示可排序並且可以按照索引訪問對象的非泛型集合(列表)。IList 是 ICollection 介面的子代,並且是所有非泛型列表的基介面,IList 實現有三種類別:只讀、固定大小和可變大小。無法修改只讀 IList。固定大小的 IList 不允許添加或移除元素,但允許修改現有元素。可變大小的 IList 允許添加、移除和修改元素。
- IDictionary:IDictionary 可以稱為字典、映射或者散列表,表示鍵/值對的非通用集合,即類似於IList但提供了可通過鍵值(而不是索引)訪問的項列表。IDictionary是Icollection介面的子代,是鍵/值對的的集合的基介面,IDictionary 實現有三種類別:只讀、固定大小、可變大小。無法修改只讀 IDictionary 對象。固定大小的 IDictionary 對象不允許添加或移除元素,但允許修改現有元素。可變大小的 IDictionary 對象允許添加、移除和修改元素。C# 語言中的 foreach 語句需要集合中每個元素的類型。由於 IDictionary 對象的每個元素都是一個鍵/值對,因此元素類型既不是鍵的類型,也不是值的類型,而是 DictionaryEntry 類型。
System.Collections命名空間中常用集合類的使用
ArrayList
實現的介面:IList、ICollections、IEnumerable
使用大小可按需動態增加的數組實現 IList 介面(大小可變的數組列表),ArrayList 的預設初始容量為 0。隨著元素添加到 ArrayList 中,容量會根據需要通過重新分配自動增加。ArrayList 接受 空引用作為有效值並且允許重覆的元素。
ArrayList把所有元素都當作object對象引用,因而在訪問ArrayList的元素時要進行類型轉換 優點:動態改變大小、靈活方便的插入和刪除元素、可排序 缺點:插入時性能不如數組、不是強類型的 示例 下麵的代碼示例演示如何創建並初始化 ArrayList 以及如何列印出其值。using System; using System.Collections; public class SamplesArrayList { public static void Main() { // Creates and initializes a new ArrayList. ArrayList myAL = new ArrayList(); myAL.Add("Hello"); myAL.Add("World"); myAL.Add("!"); // Displays the properties and values of the ArrayList. Console.WriteLine( "myAL" ); Console.WriteLine( " Count: {0}", myAL.Count ); Console.WriteLine( " Capacity: {0}", myAL.Capacity ); Console.Write( " Values:" ); PrintValues( myAL ); } public static void PrintValues( IEnumerable myList ) { foreach ( Object obj in myList ) Console.Write( " {0}", obj ); Console.WriteLine(); } } /* This code produces output similar to the following: myAL Count: 3 Capacity: f Values: Hello World ! */
Queue
實現的介面:ICollection、IEnumerable
Queue是隊列,先進先出的訪問元素。可以調用Queque對象的GetEnumerator()方法,得到IEnumerator對象,來遍歷隊列中的各個元素。Queue 的預設初始容量為 32。向 Queue 添加元素時,將通過重新分配來根據需要自動增大容量。可通過調用 TrimToSize 來減少容量。Queue 接受 空引用(在 Visual Basic 中為 Nothing) 作為有效值並且允許重覆的元素。
下麵的示例說明如何創建 Queue 並向其添加值,以及如何列印出其值。using System; using System.Collections; public class SamplesQueue { public static void Main() { // Creates and initializes a new Queue. Queue myQ = new Queue(); myQ.Enqueue("Hello"); myQ.Enqueue("World"); myQ.Enqueue("!"); // Displays the properties and values of the Queue. Console.WriteLine( "myQ" ); Console.WriteLine( "\tCount: {0}", myQ.Count ); Console.Write( "\tValues:" ); PrintValues( myQ ); } public static void PrintValues( IEnumerable myCollection ) { foreach ( Object obj in myCollection ) Console.Write( " {0}", obj ); Console.WriteLine(); } } /* This code produces the following output. T myQ Count: 3 Values: Hello World ! */
Stack
實現的介面:ICollection、IEnumerable
Stack是堆棧,先進後出的訪問各個元素。
下麵的示例說明如何創建 Stack並向其添加值,以及如何列印出其值。using System; using System.Collections; public class SamplesStack { public static void Main() { // Creates and initializes a new Stack. Stack myStack = new Stack(); myStack.Push("Hello"); myStack.Push("World"); myStack.Push("!"); // Displays the properties and values of the Stack. Console.WriteLine( "myStack" ); Console.WriteLine( "\tCount: {0}", myStack.Count ); Console.Write( "\tValues:" ); PrintValues( myStack ); } public static void PrintValues( IEnumerable myCollection ) { foreach ( Object obj in myCollection ) Console.Write( " {0}", obj ); Console.WriteLine(); } } /* This code produces the following output. myStack Count: 3 Values: ! World Hello */
HashTable
實現介面:IDictionary、ICollection、IEnumerable
表示鍵/值對的集合,這些鍵/值對根據鍵的哈希代碼進行組織。可以想HasTable中自由添加和刪除元素,有些像ArrayList,但沒有那麼大的性能開銷。
每個元素都是一個存儲在 DictionaryEntry 對象中的鍵/值對。鍵不能為 空引用(在 Visual Basic 中為 Nothing),但值可以。
當把某個元素添加到 Hashtable 時,將根據鍵的哈希代碼將該元素放入存儲桶中。該鍵的後續查找將使用鍵的哈希代碼只在一個特定存儲桶中搜索,這將大大減少為查找一個元素所需的鍵比較的次數。
下麵的示例說明如何創建 Stack並向其添加值,以及如何列印出其值。using System; using System.Collections; class Example { public static void Main() { // Create a new hash table. // Hashtable openWith = new Hashtable(); // Add some elements to the hash table. There are no // duplicate keys, but some of the values are duplicates. openWith.Add("txt", "notepad.exe"); openWith.Add("bmp", "paint.exe"); openWith.Add("dib", "paint.exe"); openWith.Add("rtf", "wordpad.exe"); // The Add method throws an exception if the new key is // already in the hash table. try { openWith.Add("txt", "winword.exe"); } catch { Console.WriteLine("An element with Key = \"txt\" already exists."); } // The Item property is the default property, so you // can omit its name when accessing elements. Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // The default Item property can be used to change the value // associated with a key. openWith["rtf"] = "winword.exe"; Console.WriteLine("For key = \"rtf\", value = {0}.", openWith["rtf"]); // If a key does not exist, setting the default Item property // for that key adds a new key/value pair. openWith["doc"] = "winword.exe"; // The default Item property throws an exception if the requested // key is not in the hash table. try { Console.WriteLine("For key = \"tif\", value = {0}.", openWith["tif"]); } catch { Console.WriteLine("Key = \"tif\" is not found."); } // ContainsKey can be used to test keys before inserting // them. if (!openWith.ContainsKey("ht")) { openWith.Add("ht", "hypertrm.exe"); Console.WriteLine("Value added for key = \"ht\": {0}", openWith["ht"]); } // When you use foreach to enumerate hash table elements, // the elements are retrieved as KeyValuePair objects. Console.WriteLine(); foreach( DictionaryEntry de in openWith ) { Console.WriteLine("Key = {0}, Value = {1}", de.Key, de.Value); } // To get the values alone, use the Values property. ICollection valueColl = openWith.Values; // The elements of the ValueCollection are strongly typed // with the type that was specified for hash table values. Console.WriteLine(); foreach( string s in valueColl ) { Console.WriteLine("Value = {0}", s); } // To get the keys alone, use the Keys property. ICollection keyColl = openWith.Keys; // The elements of the KeyCollection are strongly typed // with the type that was specified for hash table keys. Console.WriteLine(); foreach( string s in keyColl ) { Console.WriteLine("Key = {0}", s); } // Use the Remove method to remove a key/value pair. Console.WriteLine("\nRemove(\"doc\")"); openWith.Remove("doc"); if (!openWith.ContainsKey("doc")) { Console.WriteLine("Key \"doc\" is not found."); } } } /* This code example produces the following output: An element with Key = "txt" already exists. For key = "rtf", value = wordpad.exe. For key = "rtf", value = winword.exe. For key = "tif", value = . Value added for key = "ht": hypertrm.exe Key = dib, Value = paint.exe Key = txt, Value = notepad.exe Key = ht, Value = hypertrm.exe Key = bmp, Value = paint.exe Key = rtf, Value = winword.exe Key = doc, Value = winword.exe Value = paint.exe Value = notepad.exe Value = hypertrm.exe Value = paint.exe Value = winword.exe Value = winword.exe Key = dib Key = txt Key = ht Key = bmp Key = rtf Key = doc Remove("doc") Key "doc" is not found. */
上面提到的幾種集合類,他們都是通用的集合類,他們所接受的元素大都是Object類型,當對象放入 了集合之後,都失去了原有的類型信息-即這些通用集合類都不是強類型的。 比如:
ArrayList list = new ArrayList(); list.Add(new Class1()); ((Class1)list[0]).function();
list[0]無法直接調用Class1的方法function,因為在ArrayList中的各項都是System.Object類型的,但是可以通過數據類型轉換實現(多態性)。
我們如何避免數據類型的轉換呢?解決辦法是使用強類型的集合類。
定義強類型集合 創建自己的強類型集合一種方式是手動實現需要的方法,但這較費時間,某些情況下也非常複雜。我們可以從System.Collections命名空間下的CollectionBase,DictionaryBase,ReadOnlyCollectionBase 類 中派生自己的集合,或者System.Collections.Specialized命名空間下的一些類可以滿足要求,可以直接使用也可以繼承。 System.Collections.CollectionBase這個抽象類提供了集合類的大量實現代碼,這是推薦使用的方式。 這個類有介面ICollection、IEnumerable、Ilist,但值提供了一些必要的實現代碼,主要是IList的Clear()、和RemoveAt()方法以及ICollection的Count屬性。
CollectionBase還提供了兩個受保護的屬性List、InnerList,《c#入門經典》中的描述是:我們可以使用List和InnerList,其中通過List屬性可以調用IList介面訪問項目,InnerList則是用於存儲項目的ArrayList對象。
示例:
有三個類Animal、Cow、Chicken,其中Cow、Chicken是抽象類Animal的派生類,我們要創建一個Animal類的集合類Animals來存儲Animal對象。
Animal
public abstract class Animal { protected string name; public string Name { get { return name; } set { name = value; } } public Animal() { name = "The animal with no name"; } public Animal(string newName) { name = newName; } public void Feed() { Console.WriteLine("{0} has been fed.", name); } }
Cow
public class Cow : Animal { public void Milk() { Console.WriteLine("{0} has been milked.", name); } public Cow(string newName) : base(newName) { } }
Chicken
public class Chicken : Animal { public void LayEgg() { Console.WriteLine("{0} has laid an egg.", name); } public Chicken(string newName) : base(newName) { } }
存儲Animal對象的集合類可以定義如下:
public class Animals : CollectionBase { public void Add(Animal newAnimal) { List.Add(newAnimal); } public void Remove(Animal newAnimal) { List.Remove(newAnimal); } }
前面說過CollectionBase實現的介面只提供了IList的Clear()及RemoveAt()方法和ICollection的Count屬性,想要實現向集合中添加及刪除對象就需要自己來實現。我們實現了Add方法及Remove方法。
我們通過CollectionBase的List屬性使用了IList介面中用於訪問項的標準Add方法,並且限制了只能處理Animal類或派生於Animal的類,而前面介紹的ArrayList的實現代碼可以處理任何對象不是強類型的。
這樣我們就可以向Animals集合類中添加對象
Animals animalCollection = new Animals(); animalCollection.Add(new Cow("Jack")); animalCollection.Add(new Chicken("Vera"));
但是我們不能使用下麵的代碼
animalCollection[0].Feed();
要通過索引的方式來訪問項,就需要使用索引符。
索引符(索引器)
索引符是一個特殊類型的屬性,可以把它添加到一個類中,以提供類似於數組的訪問。
定義索引器的方式與定義屬性有些類似,其一般形式如下:
[修飾符] 數據類型 this[索引類型 index] { get{//獲得屬性的代碼} set{ //設置屬性的代碼} }
數據類型是將要存取的集合元素的類型。索引類型表示該索引器使用哪一種類型的索引來存取集合元素,可以是整數,可以是字元串。this表示操作本對象deep集合成員,可以簡單理解成索引器的名字,因此索引器不能具有用戶定義的名稱。
示例:
class Z { //可容納100個整數的整數集 private long[] arr = new long[100]; //聲明索引器 public long this[int index] { get { //檢查索引範圍 if (index < 0 || index >= 100) { return 0; } else { return arr[index]; } } set { if (!(index < 0 || index >= 100)) { arr[index] = value; } } }
這樣我們就可以像訪問數組一樣訪問類型Z中的arr數組
Z z=new z(); z[0]=100; z[1]=101; Console.WriteLine(z[0]);
C#中並不將索引器的類型限製為整數。例如,可以對索引器使用字元串。通過搜索集合內的字元串並返回相應的值,可以實現此類的索引器。由於訪問器可以被重載,字元串和整數版本可以共存。
class DayCollection { string[] days={"Sun","Mon","Tues","Wed","Thurs","Fri","Sat"}; private int GetDay(string testDay) { int i=0; foreach(string day in days) { if(day==testDay) return i; i++; } return -1; } public int this[string day] { get{return (GetDay(day))} } } static void Main(string[] args) { DayCollection week=new DayCollection(); Console.WriteLine("Fri:{0}",week["Fri"]); Console.WriteLine("ABC:{0}",week["ABC"]); }
結果:Fri:5
ABC:-1
介面中的索引器
在介面中也可以聲明索引器,介面索引器與類索引器的區別有兩個:一是介面索引器不使用修飾符;二是介面索引器只包含訪問器get或set,沒有實現語句。訪問器的用途是指示索引器是可讀寫、只讀還是只寫的,如果是可讀寫的,訪問器get或set均不能省略;如果只讀的,省略set訪問器;如果是只寫的,省略get訪問器。
public interface IAddress { string this[int index]{get;set;} string Address{get;set;} string Answer(); }
通過索引訪問自定義集合類
我們修改Animals類如下
public class Animals : CollectionBase { public void Add(Animal newAnimal) { List.Add(newAnimal); } public void Remove(Animal newAnimal) { List.Remove(newAnimal); } public Animal this[int animalIndex] { get { return (Animal)List[animalIndex]; } set { List[animalIndex] = value; } } }
這樣 Animais animaiCollection=new Animails(); 就可以通過animaiCollection[0]這種方式來訪問了。