C#集合 -- 自定义集合与代理
前面章节所讨论的集合都可以直接实例化,因此我们可以非常方便地使用这些集合类。但是如果你试图在集合添加或移除元素时添加控制,它们就不适用了。对于强类型集合,在某些情况下,你需要添加这样的控制:
- 添加或移除元素时,触发事件
- 更新由于添加或移除元素对应的属性
- 识别添加或删除元素的误操作并抛出异常
.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#集合 -- 自定义集合与代理的更多相关文章
- 《C#本质论》读书笔记(16)构建自定义集合
16.1 更多集合接口 集合类(这里指IEnumerable层次结构)实现的接口层次结构 16.1.1 IList<T>与IDictionary<TKey,TValue> 字典 ...
- 使用yield关键字让自定义集合实现foreach遍历
一般来说当我们创建自定义集合的时候为了让其能支持foreach遍历,就只能让其实现IEnumerable接口(可能还要实现IEnumerator接口) 但是我们也可以通过使用yield关键字构建的迭代 ...
- UICollectionView(集合视图)以及自定义集合视图
一.UICollectionView集合视图 其继承自UIScrollView. UICollectionView类是iOS6新引进的API,用于展示集合视图,布局 ...
- [c#基础]集合foreach的必要条件和自定义集合
引言 最近翻看了之前的学习笔记,看到foreach,记得当时老师讲的时候,有点犯浑,不是很明白,这好比,上小学时,你不会乘法口诀,但是随着时间的增长,你不自觉的都会了,也悟出个小道理,有些东西,你当时 ...
- 集合、拆箱、装箱、自定义集合的foreach
集合部分 参考:http://msdn.microsoft.com/zh-cn/library/0ytkdh4s(v=vs.110).aspx 集合类型是诸如哈希表.队列.堆栈.包.字典和列表等数据集 ...
- 十六、C# 常用集合类及构建自定义集合(使用迭代器)
常用集合类及构建自定义集合 1.更多集合接口:IList<T>.IDictionary<TKey,TValue>.IComparable<T>.ICollectio ...
- Java基础知识强化之集合框架笔记08:Collection集合自定义对象并遍历案例(使用迭代器)
1. Collection集合自定义对象并遍历案例(使用迭代器) (1)首先定义一个Student.java,如下: package com.himi.collectionIterator; publ ...
- C# 通过IEnumberable接口和IEnumerator接口实现自定义集合类型foreach功能
1.IEnumerator和IEnumerable的作用 其实IEnumerator和IEnumerable的作用很简单,就是让除数组和集合之外的类型也能支持foreach循环,至于foreach循环 ...
- C# 通过IEnumberable接口和IEnumerator接口实现泛型和非泛型自定义集合类型foreach功能
IEnumerator和IEnumerable的作用 其实IEnumerator和IEnumerable的作用很简单,就是让除数组和集合之外的类型也能支持foreach循环,至于foreach循环,如 ...
随机推荐
- 人脸识别经典算法二:LBP方法
与第一篇博文特征脸方法不同,LBP(Local Binary Patterns,局部二值模式)是提取局部特征作为判别依据的.LBP方法显著的优点是对光照不敏感,但是依然没有解决姿态和表情的问题.不过相 ...
- Linux分区,并且把新的分区挂载到指定的文件夹
本教程为在已使用的Linux系统中新加入一个硬盘. 1.fdisk –l 查看:看到新加入硬盘hdd 2.输入:fdisk /dev/hdd 3.键入m查看有哪些命令: 4.键入p查看一下硬盘hdd的 ...
- Unity3D引用dll打包发布的问题及解决
今年我们开始使用Unity3D开发MMORPG,脚本语言使用C#,这样我们就可以使用以往积累的许多类库.但是,在U3D中使用.NET dll的过程并不是那么顺利,比如我们今天遇到的这种问题. 一.问题 ...
- 深入理解javascript 中的 delete(转)
在这篇文章中作者从<JavaScript面向对象编程指南>一书中关于 delete 的错误讲起,详细讲述了关于 delete 操作的实现, 局限以及在不同浏览器和插件(这里指 firebu ...
- [MSSQL]SCOPE_IDENTITY,IDENT_CURRENT以及@@IDENTITY的区别
简单解释下SCOPE_IDENTITY函数,IDENT_CURRENT函数以及@@IDENTITY全局变量的区别 SCOPE_IDENTITY函数返回当前作用域内,返回最后一次插入数据表的标识,意思是 ...
- 团队项目--站立会议 DAY2
小组名称:D&M 参会人员:张靖颜,钟灵毓秀,何玥,赵莹,王梓萱 项目进展: 1,张靖颜:进行了对需求的分析,将项目框架搭建进行完善,辅助编写代码. 2,钟灵毓秀:相关功能的代码的完善,进行一 ...
- [WinAPI] API 4 [注册][创建][消息][第一个框架类窗口]
首先注册了窗口类,然后创建了一个窗口,创建窗口时指定的窗口的属性和窗口消息的处理函数.函数消息的处理函数大多调用系统默认函数来处理. #include<windows.h> /*全局变量* ...
- [BTS]The join order has been enforced because a local join hint is used.;Duplicate key was ignored.".
在一个客户的BizTalk Server 2013 R2环境中会报如下的ERROR,查找相关资料后,先试试停掉所有Trace. Log Name: ApplicationSource: ...
- SQL——用户定义函数
根据用户定义函数返回值的类型,可将用户定义函数分为如下三个类别: (1) 返回值为可更新表的函数 若用户定义函数包含单个 SELECT 语句且该语句可更新,则该函数返回的表也可更新,这样的函数称为内嵌 ...
- 如何利用tomcat搭建一个动态服务器
这篇文章只记录已解压缩包的方式安装,通常linux服务器上也是这样. 1.下载tomcat.zip压缩包. http://tomcat.apache.org/download-70.cgi 2.把zi ...