这两个接口决定了集合类型是侧重于通过位置索引来获取值,还是侧重于通过键来获取值。
实现这两个接口的类都必须提供索引器。
IList< T >和IDictionary< TKey , TValue >都实现了ICollection< T >
List< T >类的性质和数组相似,关键区别就是随着元素增多,这种类会自动扩展。此外,列表可通过显示调用TrimToSize()或Capacity来缩小。
**IComparable< T >和IComparer< T >**的区别很细微,却很重要。前者说“我知道如何将我自己和我的类型的另一个实例进行比较”,后者说“我知道如何比较给定类型的两个实例”。
using System;
using System.Collections.Generic;
// ...
public class Contact
{
public string FirstName { get; private set; }
public string LastName { get; private set; }
public Contact(string firstName, string lastName)
{
this.FirstName = firstName;
this.LastName = lastName;
}
}
public class NameComparison : IComparer
{
public int Compare(Contact? x, Contact? y)
{
if(Object.ReferenceEquals(x, y))
return 0;
if(x == null)
return 1;
if(y == null)
return -1;
int result = StringCompare(x.LastName, y.LastName);
if(result == 0)
result = StringCompare(x.FirstName, y.FirstName);
return result;
}
private static int StringCompare(string? x, string? y)
{
if(Object.ReferenceEquals(x, y))
return 0;
if(x == null)
return 1;
if(y == null)
return -1;
return x.CompareTo(y);
}
}
实现IComparable< T >和IComparer< T >时必须生成一个全序(total order),必须为任何可能的数据项排列组合提供一致的排序结果。例如上面代码中连实参是null的情况都考虑到了,不能任何一个元素为null就返回0,否则可能出现两个非null元素等于null但不相等的情况。
可以使用Contains(),Indexof(),LastIndexOf()和BinarySearch()方法。
BinarySearch()要求有序,如果没有找到,会返回一个负整数。该值的取反结果是“大于被查找元素的下一个元素”的索引。没有更大的则是元素总数。
可利用Keys和Values属性只处理字典类中的键或值。返回ICollection< T >类型,返回的是对原始字典集合中的数据的引用,而不是返回拷贝。
元素是按照键排序的
链表集合,允许正向和反向遍历。(所以是双向链表)
数组,字典和列表都提供了索引器(indexer)以便根据键或索引来获取/设置成员。
interface IPair
{
T First { get; }
T Second { get; }
T this[PairItem index] { get; }
}
public enum PairItem
{
First,
Second
}
public struct Pair : IPair
{
public Pair(T first, T second)
{
First = first;
Second = second;
}
public T First { get; }
public T Second { get; }
public T this[PairItem index]
{
get
{
switch (index)
{
case PairItem.First:
return First;
case PairItem.Second:
return Second;
default:
throw new NotImplementedException(
$"The enum { index.ToString() } has not been implemented");
}
}
}
}
索引器的声明和属性很相似,但不是使用属性名,而是使用关键字this,后跟方括号中的参数列表。主题也像属性,有get和set块。索引可获得多个参数,甚至可以重载。
返回数组和集合时允许返回null,更好的选择是返回不含任何数据项的集合实例。可避免强迫调用者在便利集合前检查null值。
本节讨论如何利用迭代器(iterator)为自定义集合实现自己的IEnumerator< T >,IEnumerable< T >和对应的非泛型接口。迭代器使集合的用户能遍历集合的内部结构,同时不必了解结构的内部实现。
类要支持用foreach进行迭代,就必须实现枚举数(enumerator)模式,如第15章所述,C#的foreach循环结构被编译器扩展成while循环结构,它以从IEnumerable< T >接口获取的IEnumerator< T >接口为基础。
迭代器提供了迭代器接口(IEnumerable< T >和IEnumerator< T >)的一个快捷实现。
using System.Collections;
using System.Collections.Generic;
public class BinaryTree : IEnumerable
{
public BinaryTree(T value)
{
Value = value;
}
#region IEnumerable
public IEnumerator GetEnumerator()
{
// ...
}
#endregion IEnumerable
public T Value { get; }
public Pair> SubItems { get; set; }
}
public struct Pair
{
public Pair(T first, T second) : this()
{
First = first;
Second = second;
}
public T First { get; }
public T Second { get; }
}
要为GetEnumerator()提供一个实现。
迭代器类似于函数,但它不是返回(return)一个值,而是**生成(yield)**一系列值。
每次迭代器遇到yield return语句都生成一个值,之后控制立即回到请求数据项的调用者。当调用者请求下一项时,慧紧接着在上一个yield return语句之后执行。
GetEnumerator()在foreach语句中被首次调用时,慧创建一个迭代器对象,其状态被初始化为特殊的“起始”状态,表示迭代器尚未执行代码,所以尚未生成任何值。
只要foreach继续,迭代器就会一直持续其状态。循环每一次请求下一个值,控制就会一直维持其状态。循环每一次请求下一个值,控制就会进入迭代器,从上一次离开的位置继续。该位置是根据迭代器对象中存储的状态信息来判断的。foreach终止,迭代器的状态就不再保存了。
可以使用yield break使MoveNext()返回false,使控制立即回到调用者并终止循环。
C#编译器遇到一个迭代器时,会根据枚举数模式将代码展开成恰当的CIL,在生成的CIL代码中,C#编译器首先创建一个嵌套的私有类来实现IEnumerator< T >接口,以及它的Current熟悉和MoveNext()方法。Current属性返回与迭代器的返回类型对应的一个类型。
using System;
using System.Collections;
using System.Collections.Generic;
// ...
[NullableContext(1)]
[Nullable(0)]
public struct Pair<[Nullable(2)] T> : IPair, IEnumerable, IEnumerable
{
public Pair(T first, T second)
{
First = first;
Second = second;
}
public T First { get; }
public T Second { get; }
public T this[PairItem index]
{
get
{
PairItem pairItem = index;
PairItem pairItem2 = pairItem;
T result;
if (pairItem2 != PairItem.First)
{
if (pairItem2 != PairItem.Second)
{
throw new NotImplementedException(
string.Format(
"The enum {0} has not been implemented",
index.ToString()));
}
result = Second;
}
else
{
result = First;
}
return result;
}
}
public IEnumerator GetEnumerator()
{
yield return First;
yield return Second;
yield break;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
yield关键字是上下文关键字,不是保留关键字。所以可以合法地声明名为yield的局部变量。
事实上,C#1.0之后加入的所有关键字都是上下文关键字,这是为了防止升级老程序来使用语言的新版本时出问题。
只有在返回IEnumerator< T >或者IEnumerable< T >类型的成员中,才能使用yield return语句。
主体包含yield return语句的成员不能包含简单return语句。
yield语句只能在方法,用户自定义操作符或者索引器/属性的get访问器方法中出现。成员不可获取任何ref或者out参数。
yield语句不能在匿名方法或Lambda表达式中出现。
yield语句不能在try语句的catch和finally块中出现。此外,yield语句在try块中出现的前提是没有catch块。