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循环,如 ...
随机推荐
- 用dom4j解析xml 报java.lang.NoClassDefFoundError:org/jaxen/JaxenException
转自:http://www.myexception.cn/java%20exception/95.html 源码如下: import java.io.File; import java.util.Li ...
- webAPI 自动生成帮助文档
之前在项目中有用到webapi对外提供接口,发现在项目中有根据webapi的方法和注释自动生成帮助文档,还可以测试webapi方法,功能很是强大,现拿出来与大家分享一下. 先看一下生成的webapi文 ...
- HTTP权威指南阅读笔记五:Web服务器
Web服务器会做些什么: 1.建产连接:接受一个客户端连接,或者如果不希望与这个客户端建立连接,就将其关闭. 1)处理新连接 2)客户端主机名识别 3)通过ident确定客户端用户 ident在组织内 ...
- Mathematica修改默认字体
1. 打开Option Inspector 2. 第一个下拉框选择Global Preference, 搜索stylehints 3. 修改字体为想要换的字体FamilyName, 比如换成苹果黑体 ...
- 易出错的C语言题目之一:宏定义与预处理
1.写出下列代码的运行结果: #include<stdio.h> #include<string.h> #define STRCPY(a,b) strcpy(a##_p,#b) ...
- atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7
atitit.文件上传带进度条的实现原理and组件选型and最佳实践总结O7 1. 实现原理 1 2. 大的文件上传原理::使用applet 1 3. 新的bp 2 1. 性能提升---分割小文件上传 ...
- chrome浏览器扩展的事件处理
关于chrome扩展开发的栗子已经有很多了,问问度娘基本能满足你的欲望, 我想说的是扩展和页面间的数据传递问题. 我们知道写扩展有个必须的文件就是“manifest.json”, 这个里面定义了一个和 ...
- Delphi的VCL组件库
Visual Component Library的缩写(可视组件库)VCL是Visual Component Library的缩写,即可视组件库,它是Delphi,C++Builder等编程语言的基本 ...
- Django简介
Django, 应该读作jan go,读音演示 D是不发音的---发音的---音的---的--- django简介:urls.py网址入口,关联到views.py views.py处理用户发出的请求, ...
- 设想 Docker 下部署 KVM
设想 Docker 下部署 KVM 一.安装 $ yum -y install kvm # kvm base , must $ yum -y install libvirt -y # libvirtd ...