十六、创建自定义集合
16.1 集合接口
IList
IDictionary
泛型集合在 System.Collections.Generics 命名空间下 非泛型集合在 System.Collections 命名空间下
16.2 集合类
16.2.1 List
16.2.1.1 排序
IComparable<T>、IComparable
写在被排序的类中
List.Sort()会自动调用 集合类对接口的实现只能有一种实现方式 IComparer<T>
与排序方法绑定
List.Sort()接收IComparer<T>类型的参数进行排序 一种排序方式新建一个方法的实现类 全序
IComparable<T>和IComparer<T>的实现都必须为任何可能的数据项的排列组合提供一致的排序结果 全序条件
比较方式
引用类型存的地址是否相等,==或者Object.ReferenceEquals() 值是否相等
具体的比较规则不同,例如,每个成员是否相等 不相等的返回值,大于与小于返回值的符号应该相反
16.2.1.2 搜索
Contains():从头开始 IndexOf():从头开始 LastIndexOf():从尾开始 BinarySearch()
只能针对有序集合 若无查找结果,则返回结果为负数
返回结果按位取反的结果是大于被查找元素的下一个元素的索引,即元素在集合中的排序位置 FindAll()
接收泛型委托类型作为参数,Predicate<T> public delegate bool Predicate( T obj )
16.2.2 Dictionary
16.2.2.1 基本操作
添加元素
读取元素
KeyValuePair<TKey,TValue>
Dictionary<TKey, TValue>中的元素类型
16.2.2.2 自定义字典的相等性
实现IEqualityComparer<T>、IEquatable<T>接口
IEqualityComparer<T>
针对比较方法,实现Equals()和GetHashCode() 通过集合的构造方法传入实例参数 IEquatable<T>
相等性比较的要求
对象相等必定散列码相等,但散列码相等对象不一定相等 两次运行程序相同成员的对象的散列码可能不相等
16.2.3 SortedDictionary、SortedList
SortedDictionary<TKey,TValue>
可按索引和键读取 按索引读取键或者值需要利用Keys和Values属性得到IList<T>
16.2.4 Stack
无法使用初始化构造器,Stack没有Add()方法 出栈入栈
读取栈顶元素
16.2.5 Queue
16.2.6 LinkedList
16.3 索引器
public enum PairItem
{
First,
Second
}
interface IPair < T>
{
T First { get ; }
T Second { get ; }
T this [ PairItem index] { get ; }
}
public struct Pair< T> : IPair< T>
{
public T First { get ; }
public T Second { get ; }
public Pair ( T first, T second)
{
First = first;
Second = second;
}
public T this [ PairItem index]
{
get
{
switch ( index)
{
case PairItem. First:
return First;
case PairItem. Second:
return Second;
default :
throw new NotImplementedException ( ) ;
}
}
}
}
参数可以定义为可变参数 Item属性
CIL代码会将索引器自动转化为一个标识符为Item的特殊属性,该属性能显式接收实参 定义了索引器之后就不能再使用Item标识符,会与索引器冲突 [System.Runtime.CompilerServices.IndexerName(string itemName) ] 可以为索引器重命名,但不会进入元数据,无法使用反射获取
16.4 迭代器
16.4.1 迭代器的作用
foreach遍历集合必须实现枚举数(enumerator)模式 foreach等价代码
Stack< int > stack = new Stack < int > ( ) ;
Stack. Enumerator enumerator = stack. GetEnumerator ( ) ;
while ( enumerator. MoveNext ( ) )
{
number = enumerator. Current;
Console. WriteLine ( number) ;
}
迭代器是实现这种模式的一种语法简化形式
只需考虑在GetEnumerator()<T>方法中如何通过 yield return 返回一系列值 编译器会将迭代器转换为状态机,可以记录当前状态和位置
16.4.2 迭代器的实现
实现IEnumerable<T>接口的GetEnumerator()<T>方法,该方法返回IEnumerator<T>类型的对象
GetEnumerator()方法就是定义如何遍历集合 根据集合结构将所有值通过 yield return 分别返回 迭代器能知道执行到哪个yield return,并在下一次执行时继续 可以用 yield break 取消迭代 IEnumerable<T>继承自IEnumerable,所以也应该实现GetEnumerator()方法 可能会使用递归算法,但层次较深的应避免使用递归
public class CSharpBuiltInTypes : IEnumerable < string >
{
public IEnumerator< string > GetEnumerator ( )
{
yield return "object" ;
yield return "byte" ;
yield return "uint" ;
. . .
yield return "string" ;
}
IEnumerator IEnumerable. GetEnumerator ( )
{
return GetEnumerator ( ) ;
}
}
public class Program
{
static void Main ( )
{
var keywords = new CSharpBuildInTypes ( ) ;
foreach ( var keyword in keywords)
{
Console. WriteLine ( keyword) ;
}
}
}
16.4.3 迭代器的工作原理
编译器会将迭代器转换为enumerator模式 CIL中会在集合类内部生成 private sealed 的嵌套类
该类实现IEnumerator<T>接口 作为GetEnumerator()<T>方法的返回对象的类型
包含Current属性 包含MoveNext()方法 根据GetEnumerator()<T>方法中的代码自动生成MoveNext()方法,实现enumerator模式 和手动实现enumerator模式的性能基本一致
16.4.4 在单个类中创建多个迭代器
作用
在集合中定义多个类似GetEnumerator()<T>的方法
方法名重新定义 返回类型为IEnumerator<T>类型 在foreach中的调用方式
foreach ( T item in 集合实例.Get…Enumerator() )