前面章节所讨论的集合都可以直接实例化,因此我们可以非常方便地使用这些集合类。但是如果你试图在集合添加或移除元素时添加控制,它们就不适用了。对于强类型集合,在某些情况下,你需要添加这样的控制:

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

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

可自定义集合类通常都作为public的集合使用。比如,System.Windows.Form类中的集合控件。

Collection<T>类与CollectionBase类

Collection<T>是List<T>的可自定义的包装器。

与IList<T>和IList实现一样,它还定义了四个额外的虚方法和一个protected属性

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属性允许实现者直接访问“内部列表”--通过这种方式在内部实现变化而不触发虚方法。

虚方法需要重写;它们可以不用考虑直到有需求更改集合的默认行为。下面的例子演示了一个集合应有的“骨架”:

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>的每个虚方法以自动更新所影响的属性。

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>所做的更改会影响到集合具体实现类。

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

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>

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

C#集合 -- 自定义集合与代理的更多相关文章

  1. 《C#本质论》读书笔记(16)构建自定义集合

    16.1 更多集合接口 集合类(这里指IEnumerable层次结构)实现的接口层次结构 16.1.1 IList<T>与IDictionary<TKey,TValue> 字典 ...

  2. 使用yield关键字让自定义集合实现foreach遍历

    一般来说当我们创建自定义集合的时候为了让其能支持foreach遍历,就只能让其实现IEnumerable接口(可能还要实现IEnumerator接口) 但是我们也可以通过使用yield关键字构建的迭代 ...

  3. UICollectionView(集合视图)以及自定义集合视图

    一.UICollectionView集合视图           其继承自UIScrollView.         UICollectionView类是iOS6新引进的API,用于展示集合视图,布局 ...

  4. [c#基础]集合foreach的必要条件和自定义集合

    引言 最近翻看了之前的学习笔记,看到foreach,记得当时老师讲的时候,有点犯浑,不是很明白,这好比,上小学时,你不会乘法口诀,但是随着时间的增长,你不自觉的都会了,也悟出个小道理,有些东西,你当时 ...

  5. 集合、拆箱、装箱、自定义集合的foreach

    集合部分 参考:http://msdn.microsoft.com/zh-cn/library/0ytkdh4s(v=vs.110).aspx 集合类型是诸如哈希表.队列.堆栈.包.字典和列表等数据集 ...

  6. 十六、C# 常用集合类及构建自定义集合(使用迭代器)

    常用集合类及构建自定义集合 1.更多集合接口:IList<T>.IDictionary<TKey,TValue>.IComparable<T>.ICollectio ...

  7. Java基础知识强化之集合框架笔记08:Collection集合自定义对象并遍历案例(使用迭代器)

    1. Collection集合自定义对象并遍历案例(使用迭代器) (1)首先定义一个Student.java,如下: package com.himi.collectionIterator; publ ...

  8. C# 通过IEnumberable接口和IEnumerator接口实现自定义集合类型foreach功能

    1.IEnumerator和IEnumerable的作用 其实IEnumerator和IEnumerable的作用很简单,就是让除数组和集合之外的类型也能支持foreach循环,至于foreach循环 ...

  9. C# 通过IEnumberable接口和IEnumerator接口实现泛型和非泛型自定义集合类型foreach功能

    IEnumerator和IEnumerable的作用 其实IEnumerator和IEnumerable的作用很简单,就是让除数组和集合之外的类型也能支持foreach循环,至于foreach循环,如 ...

随机推荐

  1. JAVA学习博客----2015.4

    这是开始写的第一篇学习博客,记录一下每个月的学习进度和掌握程度,因为这是第一次写学习博客而且已经看编程方面的书已经有几个月了,所以这一篇学习博客可能看似有些乱或者篇幅太长.从十二月的<程序员教程 ...

  2. Zabbix3.0 自动电话报障

    第一种:Pagerduty 网站:www.pagerduty.com 优点:老牌服务商,稳定 缺点:贵,英文,网站要FQ 价格参考(34美元每月才25个电话,*29每月是包年才有的价格) 安装方式: ...

  3. SQLSERVER2014 2014年4月1日发布

    SQLSERVER2014 2014年4月1日发布 原文地址: http://blogs.technet.com/b/microsoft_blog/archive/2014/03/18/sql-ser ...

  4. Java 中的反射机制

    JAVA反射机制 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法:这种动态获取的信息以及动态调用对象的方法的功能称为ja ...

  5. Windows7上搭建Cocos2d-x 3.1.1开发环境

    前言 现在,越来越多的公司采用Cocos2d-x 3.0来开发游戏了,但是现在这样的文章并不多,所以打算写一系列来帮助初学者快速掌握Cocos2d-x 3.0.首先就从开发环境的大家说起吧. 开发工具 ...

  6. 在Linux CentOS上编译CoreCLR

    经过几天的努力,终于解决了在CentOS上编译CoreCLR的问题.最终发现问题是CMAKE_C_FLAGS的设置引起的. 只要在“src/pal/tools/clang-compiler-overr ...

  7. Flash矢量图与位图性能对比

    Flash中使用位图的性能要高于矢量图,究竟有多大区别呢?数据有最好的说服力,开始测试: 一.机器配置 二.测试过程 测试程序控制红色小球在舞台中不停匀速移动,通过改变小球数量控制实际帧率在24帧/秒 ...

  8. [MFC] MFC音乐播放器 傻瓜级教程 网络 搜索歌曲 下载

    >目录< >——————————————————————< 1.建立工程  1.建立一个MFC工程,命名为Tao_Music 2.选择为基本对话框 3.包含Windows So ...

  9. 自旋锁-SpinLock(.NET 4.0+)

    短时间锁定的情况下,自旋锁(spinlock)更快.(因为自旋锁本质上不会让线程休眠,而是一直循环尝试对资源访问,直到可用.所以自旋锁线程被阻塞时,不进行线程上下文切换,而是空转等待.对于多核CPU而 ...

  10. mysql 5.7 win7 压缩版安装

    1.下载mysql压缩版并解压: 2.复制my-defualt.ini , 命名为my.ini; 3. 3.1 运行在下图bin目录下运行:mysqld --install   安装mysql服务: ...