前面章節所討論的集合均可以直接實例化,所以咱們能夠很是方便地使用這些集合類。可是若是你試圖在集合添加或移除元素時添加控制,它們就不適用了。對於強類型集合,在某些狀況下,你須要添加這樣的控制:ide
.NET Framework爲上述目的提供了集合類,它們位於System.Collections.ObjectModel命名空間下。這些代理或包裝類類經過在擴展類實現所需的方法從而實現了ILIst<T>或IDictionary<TKey,TValue>類。每一個Add,Remove和Clear操做都被標記爲虛方法,從而當它們被重寫時能夠充當一個入口的做用。函數
可自定義集合類一般都做爲public的集合使用。好比,System.Windows.Form類中的集合控件。ui
Collection<T>是List<T>的可自定義的包裝器。this
與IList<T>和IList實現同樣,它還定義了四個額外的虛方法和一個protected屬性spa
public class Collection<T>: IList<T>, IList, IReadOnlyList<T> { IList<T> items; protected IList<T> Items { get { return items; } } // ... protected virtual void ClearItems() { items.Clear(); } protected virtual void InsertItem(int index, T item) { items.Insert(index, item); } protected virtual void RemoveItem(int index) { items.RemoveAt(index); } protected virtual void SetItem(int index, T item) { items[index] = item; } //... }
虛方法提供了一個入口,經過該入口,你能夠勾住該入口以更改或加強默認的行爲。而Items屬性容許實現者直接訪問「內部列表」--經過這種方式在內部實現變化而不觸發虛方法。3d
虛方法須要重寫;它們能夠不用考慮直到有需求更改集合的默認行爲。下面的例子演示了一個集合應有的「骨架」:代理
public class Animal { public string Name; public int Popularity; public Animal (string name, int popularity) { Name = name; Popularity = popularity; } } public class AnimalCollection : Collection <Animal> { // AnimalCollection is already a fully functioning list of animals. // No extra code is required. } public class Zoo // The class that will expose AnimalCollection. { // This would typically have additional members. public readonly AnimalCollection Animals = new AnimalCollection(); } class Program { static void Main() { Zoo zoo = new Zoo(); zoo.Animals.Add (new Animal ("Kangaroo", 10)); zoo.Animals.Add (new Animal ("Mr Sea Lion", 20)); foreach (Animal a in zoo.Animals) Console.WriteLine (a.Name); } }
AnimalCollection除了只是一個List<Animal>以外,沒有其餘任何多餘的功能;它的角色是爲了未來的擴展須要。爲了證明這點,如今咱們須要添加Zoo屬性到Animal,從而AnimalCollection能夠引用Zoo對象,從而代表Animal屬於哪一個Zoo;而且AnimalCollection類重寫Collection<Animal>的每一個虛方法以自動更新所影響的屬性。code
public class Animal { public string Name; public int Popularity; public Zoo Zoo { get; internal set; } public Animal(string name, int popularity) { Name = name; Popularity = popularity; } } public class AnimalCollection : Collection <Animal> { Zoo zoo; public AnimalCollection (Zoo zoo) { this.zoo = zoo; } protected override void InsertItem (int index, Animal item) { base.InsertItem (index, item); item.Zoo = zoo; } protected override void SetItem (int index, Animal item) { base.SetItem (index, item); item.Zoo = zoo; } protected override void RemoveItem (int index) { this [index].Zoo = null; base.RemoveItem (index); } protected override void ClearItems() { foreach (Animal a in this) a.Zoo = null; base.ClearItems(); } } public class Zoo { public readonly AnimalCollection Animals; public Zoo() { Animals = new AnimalCollection (this); } }
Collection<T>還有一個構造器方法,該方法接收IList<T>參數。與其餘集合類不同,該集合是一個代理而不是一個備份,這就意味着後續的更改會直接反映到包裝的Collection<T>中(儘管並無觸發Collection<T>的虛方法)。相反,對COllection<T>所作的更改會影響到集合具體實現類。orm
CollectionBase對象
CollectionBase是非generic的Collection<T>,它在.NET Framework 1.0中就已經存在。它提供了與Collection<T>大多數功能,可是它使用起來很是笨拙。在CollectionBase類中,沒有IntertItem, RemoveITem, SetITem和ClearItem方法,取代它們的是OnInsert, OnInsertComplete, OnSet, OnSetComplete, OnRemove, OnRemoveComplete, OnClear和OnClearComplete方法。由於CollectionBase是非generic的,當你繼承該類時,你必須實現類型化的方法--至少,須要一個類型化的索引器和類型化的Add方法。
KeyedCollection<TKey,TItem>繼承Collection<T>。它既添加又刪除了一些功能。添加的方法包括經過鍵獲取元素;刪除了內部列表的代理功能。
以鍵爲基礎的集合與OrderedDictionary類類似,由於它使用一個哈希表構建了一個線性集合。可是,與OrderedDictionary不一樣,它並無實現IDictionary,於是不支持key/value對這樣的概念。鍵並非從元素自身獲取,而是經過一個抽象的方法GetKeyFromItem。這就意味着遍歷這樣的以鍵爲基礎的集合與遍歷一個普通的集合同樣。
Collection<TItem>加經過鍵快速查找的元素就是KeyedCollection<TKey,TItem>。
由於它繼承Collection<T>,一個鍵集合繼承了Collection<T>的全部功能,除了經過構造器設定一個內部集合實例。
Collection<T>的構造器以下:
public Collection() { items = new List<T>(); } public Collection(IList<T> list) { if (list == null) { ThrowHelper.ThrowArgumentNullException(ExceptionArgument.list); } items = list; }
KeyedCollection的構造器以下:
protected KeyedCollection(): this(null, defaultThreshold) {} protected KeyedCollection(IEqualityComparer<TKey> comparer): this(comparer, defaultThreshold) {} protected KeyedCollection(IEqualityComparer<TKey> comparer, int dictionaryCreationThreshold) { if (comparer == null) { comparer = EqualityComparer<TKey>.Default; } if (dictionaryCreationThreshold == -1) { dictionaryCreationThreshold = int.MaxValue; } if( dictionaryCreationThreshold < -1) { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.dictionaryCreationThreshold, ExceptionResource.ArgumentOutOfRange_InvalidThreshold); } this.comparer = comparer; this.threshold = dictionaryCreationThreshold; }
其餘成員的定義以下面的代碼所示:
public abstract class KeyedCollection <TKey, TItem> : Collection <TItem> // ... protected abstract TKey GetKeyForItem(TItem item); protected void ChangeItemKey(TItem item, TKey newKey); // Fast lookup by key - this is in addition to lookup by index. public TItem this[TKey key] { get; } protected IDictionary<TKey, TItem> Dictionary { get; } }
GetKeyFromItem是抽象方法,由具體的實現類實現。當元素的鍵屬性發生變化時,必須調用ChangeItemKey方法,以更新內部的字典實例(Dictionary<TKey,TItem> dict;)。Dictionary屬性返回用於實現查詢的內部字典實例,該實例在向集合中插入第一個元素時自動建立。該行爲能夠經過構造器函數中的threshold參數來改變,若是執行了threshold,那麼只有當達到臨界點以後,纔會建立內部的字典實例。而不指定建立臨界點的好處是對於經過Dictionary屬性的Keys屬性,獲取ICollection的鍵而言,有一個有效的字典會很是有用。那麼,該集合就能夠做爲一個公開的屬性傳遞給調用者。
使用KeyedCollection<>的場景是經過索引或者名字獲取集合元素。爲了證明這點,請看下面的例子:
public class Animal { string name; public string Name { get { return name; } set { if (Zoo != null) Zoo.Animals.NotifyNameChange (this, value); name = value; } } public int Popularity; public Zoo Zoo { get; internal set; } public Animal (string name, int popularity) { Name = name; Popularity = popularity; } } public class AnimalCollection : KeyedCollection <string, Animal> { Zoo zoo; public AnimalCollection (Zoo zoo) { this.zoo = zoo; } internal void NotifyNameChange (Animal a, string newName) { this.ChangeItemKey (a, newName); } protected override string GetKeyForItem (Animal item) { return item.Name; } // The following methods would be implemented as in the previous example protected override void InsertItem (int index, Animal item)... protected override void SetItem (int index, Animal item)... protected override void RemoveItem (int index)... protected override void ClearItems()... } public class Zoo { public readonly AnimalCollection Animals; public Zoo() { Animals = new AnimalCollection (this); } } class Program { static void Main() { Zoo zoo = new Zoo(); zoo.Animals.Add (new Animal ("Kangaroo", 10)); zoo.Animals.Add (new Animal ("Mr Sea Lion", 20)); Console.WriteLine (zoo.Animals [0].Popularity); // 10 Console.WriteLine (zoo.Animals ["Mr Sea Lion"].Popularity); // 20 zoo.Animals ["Kangaroo"].Name = "Mr Roo"; Console.WriteLine (zoo.Animals ["Mr Roo"].Popularity); // 10 } }
DictionaryBase
KeyedCollection的非generic的版本是DictionaryBase類。該歷史類的方法與之有很大不一樣。與CollectionBase同樣,它也是經過笨拙的鉤子方式實現了IDictionary,這些鉤子方法是:OnInsert, OnInsertComplete, OnSet, OnSetComplete, OnRemove, OnRemoveComplete, OnClear和OnClearComplete方法。採用KeyedCollection方式實現IDictonary的好處是,你不須要子類來實現經過鍵獲取元素。而DictionaryBase存在的目的就是爲了建立子類,因此Dinctionary根本沒有任何優勢。正是由於這點,在後續的Framwork版本中才引入了KeyedCollection。因此若是你的程序須要保證對之前系統的兼容性,那麼使用DictionaryBase類;不然使用KeyedCollection類。
ReadOnlyCollection<T>是一個包裝器或代理,它提供了一個只讀的集合。這很是適用於一個類對外公開一個只讀的集合而對內卻能夠任意更改。
一個只讀集合能夠在構造器函數中接收一個輸入集合,而後對該集合保持一個固定的引用。它並不會對輸入集合生成一個靜態的拷貝,因此對於輸入集合後續的更改會在經過只讀包裝器中屬性中體現。
爲了演示這點,假設你但願你的類提供一個只讀的公開的屬性Names
public class Test { public List<string> Names { get; private set; } }
上面的代碼值完成了一半的工做。儘管其餘的類型不能設置Name屬性,它們仍是能夠調用該集合的Add,Remove方法。而ReadOnlyCollection就解決了這點:
public class Test { List<string> names; public ReadOnlyCollection<string> Names { get; private set; } public Test() { names = new List<string>(); Names = new ReadOnlyCollection<string> (names); } public void AddInternally() { names.Add ("test"); } }
如今,只有Test類的成員能夠修改names集合:
Test t = new Test(); Console.WriteLine (t.Names.Count); // 0 t.AddInternally(); Console.WriteLine (t.Names.Count); // 1 t.Names.Add ("test"); // Compiler error ((IList<string>) t.Names).Add ("test"); // NotSupportedException