C#集合 -- 自定义集合与代理

前面章节所讨论的集合均可以直接实例化,所以咱们能够很是方便地使用这些集合类。可是若是你试图在集合添加或移除元素时添加控制,它们就不适用了。对于强类型集合,在某些状况下,你须要添加这样的控制:ide

  • 添加或移除元素时,触发事件
  • 更新因为添加或移除元素对应的属性
  • 识别添加或删除元素的误操做并抛出异常

.NET Framework为上述目的提供了集合类,它们位于System.Collections.ObjectModel命名空间下。这些代理或包装类类经过在扩展类实现所需的方法从而实现了ILIst<T>或IDictionary<TKey,TValue>类。每一个Add,Remove和Clear操做都被标记为虚方法,从而当它们被重写时能够充当一个入口的做用。函数

可自定义集合类一般都做为public的集合使用。好比,System.Windows.Form类中的集合控件。ui

 

Collection<T>类与CollectionBase类

image

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>和DictionaryBase

image

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>

image

 

 

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
相关文章
相关标签/搜索