第7章 集合
7.1 枚举
下图展示了部分集合接口:
7.1.1 IEnumerable
和 IEnumerator
IEnumerator
声明如下:
public interface IEnumerator
{
object Current { get; }
bool MoveNext();
void Reset();
}
Reset
方法存在的目的主要是 COM 互操作 ;而其他情况应当尽量避免直接调用该方法,因为它并未得到广泛支持(另外,调用该方法并没有太大必要,因为完全可以重新实例化一个枚举器来达到相同效果)
IEnumerable
声明如下:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
通过定义一个返回 枚举器 的方法,IEnumerable
灵活地将迭代逻辑转移到了另一个类上。此外,多个消费者可以同时遍历同一个集合而 互不影响 。IEnumerable
可以看作是“IEnumerator
的提供者”,它是所有集合类型需要实现的最基础接口。
7.1.2 IEnumerable<T>
和 IEnumerator<T>
IEnumerable<T>
和 IDisposable
IEnumerator<T>
继承 IDisposable
。这样枚举器就可以保有像数据库连接这样的资源,而且可以在枚举结束后(或中途停止后)确保释放这些资源。foreach 语句能够识别这个细节。如下两段代码等价:
foreach (var element in somethingEnumerable) { ... }
using (var rator = s.GetEnumerator())
while (rator.MoveNext())
{
char c = (char) rator.Current;
Console.Write (c + ".");
}
7.1.3 实现枚举接口
要实现 IEnumerable
/IEnumerable<T>
就必须提供一个枚举器。可以采用如下三种方式实现:
- 如果这个类“包装”了任何一个集合,那么就返回所包装集合的枚举器
- 使用
yield return
来进行迭代 - 实例化自己的
IEnumerator
/IEnumerator<T>
实现
方式二:编写迭代器
调用内部集合的 GetEnumerator
方法适用面较窄(适用于 内部集合元素恰好是外部需要遍历的元素 ),更好的方法是使用 yield return
语句编写迭代器。 yield return
编写的迭代器会自动处理 IEnumerable
和 IEnumerator
及其泛型版本的实现:
public class MyCollection : IEnumerable
{
int[] data = { 1, 2, 3 };
public IEnumerator GetEnumerator()
{
foreach (int i in data)
yield return i;
}
}
public class MyGenCollection : IEnumerable<int>
{
int[] data = { 1, 2, 3 };
public IEnumerator<int> GetEnumerator()
{
foreach (int i in data)
yield return i;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
令人疑惑的是,yield return 方法的返回值既可以是
IEnumerable
,也可以是IEnumerator
:public static IEnumerable <int> GetSomeIntegers() { yield return 1; yield return 2; yield return 3; }
public static IEnumerator <int> GetSomeIntegers() { yield return 1; yield return 2; yield return 3; }
不过,foreach 语句仅能应用于
IEnumerable
对象。
IEnumerable
实例可以反复获得IEnumerator
实例,反复遍历,而IEnumerator
不行。
方式三:自定义 IEnumerator
类
实现方式如下:
public class MyIntList : IEnumerable
{
int[] data = { 1, 2, 3 };
public IEnumerator GetEnumerator() => new Enumerator (this);
class Enumerator : IEnumerator // Define an inner class
{ // for the enumerator.
MyIntList collection;
int currentIndex = -1;
internal Enumerator (MyIntList collection)
{
this.collection = collection;
}
public object Current
{
get
{
if (currentIndex == -1)
throw new InvalidOperationException ("Enumeration not started!");
if (currentIndex == collection.data.Length)
throw new InvalidOperationException ("Past end of list!");
return collection.data [currentIndex];
}
}
public bool MoveNext()
{
if (currentIndex >= collection.data.Length - 1) return false;
return ++currentIndex < collection.data.Length;
}
public void Reset() => currentIndex = -1;
}
}
class MyIntList : IEnumerable<int>
{
int[] data = { 1, 2, 3 };
public IEnumerator<int> GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
class Enumerator : IEnumerator<int>
{
int currentIndex = -1;
MyIntList collection;
internal Enumerator (MyIntList collection)
{
this.collection = collection;
}
public int Current { get { return collection.data [currentIndex]; } }
object IEnumerator.Current { get { return Current; } }
public bool MoveNext() => ++currentIndex < collection.data.Length;
public void Reset() => currentIndex = -1;
void IDisposable.Dispose() {}
}
}
C7.0 核心技术指南 第7版.pdf - p346 - C7.0 核心技术指南 第 7 版-P346-20240211120034
7.2 ICollection
和 IList
接口
IEnumerable<T>
、ICollection<T>
、IList<T>
/IDictionary<K,V>
区别如下:
-
IEnumerable<T>
/IEnumerable
:提供了最少的功能支持(仅支持元素枚举) -
ICollection<T>
/ICollection
:提供一般的功能(例如Count
属性) -
IList<T>
/IDictionary<K,V>
及其非泛型版本:支持最多的功能(包括根据索引/键进行“随机”访问)
C7.0 核心技术指南 第7版.pdf - p347 - C7.0 核心技术指南 第 7 版-P347-20240211120756
C7.0 核心技术指南 第7版.pdf - p348 - C7.0 核心技术指南 第 7 版-P348-20240211120837
7.2.1 ICollection<T>
和 ICollection
两者的接口内容分别如下:
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
int Count { get; }
bool IsReadOnly { get; }
void Add(T item);
void Clear();
bool Contains(T item);
void CopyTo(T[] array, int arrayIndex);
bool Remove(T item);
}
public interface ICollection : IEnumerable
{
int Count { get; }
object SyncRoot { get; }
bool IsSynchronized { get; }
void CopyTo(Array array, int index);
}
非泛型 ICollection
支持计数功能和一些辅助同步操作的属性。
7.2.2 IList<T>
和 IList
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
T this[int index] { get; set; }
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);
}
public interface IList : ICollection, IEnumerable
{
object? this[int index] { get; set; }
bool IsReadOnly { get; }
bool IsFixedSize { get; }
int Add(object? value);
bool Contains(object? value);
void Clear();
int IndexOf(object? value);
void Insert(int index, object? value);
void Remove(object? value);
void RemoveAt(int index);
}
可以看到,IList
对 ICollection
缺少的方法进行了补全
通用的 List<T>
类、** 数组 **实现了 IList<T>
和 IList
两种接口。(需要注意, 数组 显式实现了添加、删除方法,以对外隐藏。调用这些方法则会抛出 NotSupportedException
)。
7.2.3 IReadOnlyList<T>
IReadonlyList<T>
可以看作 IList<T>
的缩减版本,它仅仅包含列表的只读行为:
public interface IReadOnlyList<out T> : IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
T this[int index] { get; }
}
其泛型参数 T 仅用于输 出 ,因此被标记为 协 变。
C7.0 核心技术指南 第7版.pdf - p350 - C7.0 核心技术指南 第 7 版-P350-20240211124524
7.3 Array
类
Array
类是所有一维和多维数组的** 隐式基类 。当使用 C# 语法声明数组时,CLR 会在内部将其转换为 Array
类型的子类**,合成一个对应该数组维度和元素类型的伪类型。这个伪类型实现了类型化的泛型集合接口,例如 IList<string>
。
Array
本身是一个类,因此数组总是引用类型,进行相等比较时判断引用是否相同。若想比较元素是否相同,可以通过 StructuralComparisons
类型进行:
object[] a1 = { "string", 123, true };
object[] a2 = { "string", 123, true };
Console.WriteLine (a1 == a2); // False
Console.WriteLine (a1.Equals (a2)); // False
IStructuralEquatable se1 = a1;
Console.WriteLine (se1.Equals (a2, StructuralComparisons.StructuralEqualityComparer)); // True
7.3.1 创建和索引
此外,可以通过调用 Array.CreateInstance
静态方法动态创建一个数组实例。该方法可以在运行时指定元素类型、维数,以及通过指定数组下界来实现非零开始的数组(注意:非零开始的数组不符合CLS的规定。)。可以使用 GetValue
和 SetValue
方法访问数组元素(它们也可以访问普通数组元素):
Array a = Array.CreateInstance (typeof(string), 2);
a.SetValue ("hi", 0); // → a[0] = "hi";
a.SetValue ("there", 1); // → a[1] = "there";
string s = (string) a.GetValue (0); // → s = a[0];
GetValue
和 SetValue
也支持编译器创建的数组,并且它们能够在方法中处理任意类型和任意维数的数组。对于多维数组,它们接收一个索引器数组参数:
public object GetValue ( params int[] indices)
public void SetValue (object value, params int[] indices)
下面的方法会打印出任意数组的第一个元素,并可以是任意维数的数组:
void WriteFirstValue (Array a)
{
Console.Write (a.Rank + "-dimensional; ");
int[] indexers = new int[a.Rank];
Console.WriteLine ("First value is " + a.GetValue (indexers));
}
7.3.2 枚举
数组可以通过 foreach 枚举,还可以通过 Array.ForEach
进行枚举,该方法签名如下:
public static void ForEach<T>(T[] array, Action<T> action)
如下两段代码等价:
int[] myArray = { 1, 2, 3};
foreach (int val in myArray)
Console.WriteLine (val);
Array.ForEach (new[] { 1, 2, 3 }, Console.WriteLine);
7.3.3 长度和维数
Array 提供了如下的方法和属性来查询长度和维数:
-
返回 指定维度长度
-
public int GetLength(int dimension);
-
public long GetLongLength(int dimension);
-
-
返回 所有维度元素总数
-
public int Length { get; }
-
public long LongLength { get; }
-
-
返回 非零起始数组的首、尾索引
-
public int GetLowerBound (int dimension);
-
public int GetUpperBound (int dimension);
-
-
返回 数组的维度
-
public int Rank { get; }
-
7.3.4 搜索
Array
类提供了许多搜索一维数组元素的方法:
-
BinarySearch
方法在数组中二分查找特定元素,仅适用于 排序 数组。可以接受一个
ICompare
对象判断元素顺序。注意:传入的
ICompare
对象需和 排序 时所用比较器一致。 -
IndexOf
/LastIndexOf
方法通过 遍历 查找数组中的特定元素。
-
Find
/FindLast
/FindIndex
/FindAll
/Exists
/TrueForAll
查找数组中满足指定的
Predicate<T>
的一个或多个元素。其中:
-
FindAll
= Linq.Where
-
Exists
= Linq.Any
-
TrueForAll
= Linq.All
-
上述方法都不会抛异常,若查找失败,返回 -1 或 default(T)
。
7.3.5 排序
以下是 Array
内置的排序算法:
-
单个数组排序
public static void Sort<T> (T[] array); public static void Sort (Array array);
-
一对数组排序
此类排序将基于第一个数组的排序结果对第二个数组进行调整。
public static void Sort<TKey,TValue> (TKey[] keys, TValue[] items); public static void Sort (Array keys, Array items);
上述排序方法都支持接受 IComparer<T>
接口实例或 Comparison<T>
委托以自定义排序(注意,非 IComparable<T>
接口)。
C7.0 核心技术指南 第7版.pdf - p358 - C7.0 核心技术指南 第 7 版-P358-20240212100234
7.3.7 复制数组
Array 有 4 个方法对数组进行浅表复制:
-
实例方法
-
Clone()
拷贝全部 -
CopyTo()
拷贝指定范围
-
-
静态方法
-
Copy()
拷贝指定范围 -
ConstrainedCopy()
原子操作:如果有元素无法复制,则 操作回滚 。
-
Array.AsReadOnly
方法可以将数组包装成 ReadOnlyCollection
。
7.3.8 转换和调整大小
Array.ConvertAll
类似于 Linq.Select
,用于转换数组。该方法接受一个委托用于转化,该委托签名如下:
public delegate TOutput Converter<in TInput, out TOutput>(TInput input);
用法如下:
float[] reals = { 1.3f, 1.5f, 1.8f };
int[] wholes = Array.ConvertAll (reals, r => Convert.ToInt32 (r));
wholes.Dump();
Array.Resize
方法用于调整数组大小,该方法通过 ref 参数返回一个新数组。
7.4 List
、Queue
、Stack
和 Set
7.4.1 List
和 ArrayList
ArrayList
是 List<T>
的非泛型版本。
C7.0 核心技术指南 第7版.pdf - p361 - C7.0 核心技术指南 第 7 版-P361-20240212175106
可以通过 Linq 将 ArrayList
转换为 List
:
ArrayList a1 = new ArrayList();
a1.AddRange(new[] {1, 5, 9});
List<int> list = a1.Cast<int>().ToList();
7.4.2 LinkedList<T>
LinkedList<T>
为 双向链表 ,其结构如下:
LinkedList<T>
实现了 IEnumerable<T>
和 ICollection<T>
接口,未实现 IList<T>
接口(不支持索引访问)。
LinkedList<T>
也支持如下搜索方法,通过遍历每个元素实现:
public bool Contains (T value);
public LinkedListNode<T> Find (T value);
public LinkedListNode<T> FindLast (T value);
此外,可以通过 CopyTo
方法将元素复制到一个数组中,类似于 Linq.ToArray
方法。
7.4.3 Queue<T>
和 Queue
7.4.4 Stack<T>
和 Stack
7.4.5 BitArray
BitArray
用于按 位 保存 bool
值,相较 bool
数组、列表具有更高的内存使用效率。
BitArray
的索引器可以读写每一个位:
var bits = new BitArray(2);
bits[1] = true;
它提供了四种按位操作的运算符方法:And()
、Or()
、Xor()
和 Not()
。除 Not
方法外,其他的方法都接受一个 BitArray
作为参数:
bits.Xor (bits); // Bitwise exclusive-OR bits with itself
Console.WriteLine (bits[1]); // False
7.4.6 HashSet<T>
和 SortedSet<T>
HashSet<T>
通过 存储散列表 实现, SortedSet<T>
通过 红/黑树 实现,它们有如下特点:
- 它们的
Contains
方法均使用 散列 查找,因此执行速度很快。 - 它们都不保存 重复 元素,并且都忽略添加 重复 值的请求。
- 无法根据 位置 访问元素。
这两个类型都实现了 ISet<T>
、ICollection<T>
接口,除常用的方法,还提供了基于 Predicate<T>
删除元素的方法:RemoveWhere
。
HashSet<T>
构造器支持传入 IEqualityComparer<T>
用于自定义键值,SortedSet<T>
构造器支持传入 IComparer<T>
用于自定义排序方式。
7.4.6.1 HashSet<T>
和 SortedSet<T>
共有方法
这两个类型都支持通过构造函数传入 IEnumerable<T>
实例,即使是它们自身:
string str = "Hello world";
var value1 = new SortedSet<char>(str);
var value2 = new HashSet<char>(value1);
这两个类有集合的各种操作(并、交、差):
public void Unionwith (IEnumerable<T> other); // 并集
public void Intersectwith (IEnumerable<T> other); // 交集
public void Exceptwith (IEnumerable<T> other); // 差集
public void SymmetricExceptwith (IEnumerable<T> other); // 对称差集
以下为查询的各种操作:
public bool IsSubsetof (IEnumerable<T> other);
public bool IsProperSubsetof (IEnumerable<T> other);
public bool IsSupersetof (IEnumerable<T> other);
public bool IsProperSupersetof (IEnumerable<T> other);
public bool Overlaps (IEnumerable<T> other);
public bool SetEquals (IEnumerable<T> other);
7.4.6.2 SortedSet<T>
独有方法
SortedSet<T>
额外拥有如下成员:
public virtual SortedSet<T> GetViewBetween (T lowerValue, T upperValue)
public IEnumerable<T> Reverse()
public T Min { get; }
public T Max { get; }
通过 GetViewBetween
,可以获得指定范围内的元素:
var letters = new SortedSet<char> ("the quick brown fox");
foreach (char c in letters.GetViewBetween ('f', 'j'))
Console.Write (c);
7.5 字典
7.5.2 IDictionary
非泛型的 IDictionary
接口在原理上与 IDictionary<TKey,TValue>
相同,但是存在以下两个重要的功能区别:
- 试图通过索引器检索一个不存在的键会返回 null (而不是 抛出一个异常 )。
- 使用
Contains
而非ContainsKey
来检测成员是否存在。
枚举非泛型的 IDictionary
会返回一个 DictionaryEntry
结构体序列:
public struct DictionaryEntry
{
public object Key { get;set; }
public object Value { get;set; }
}
7.5.3 Dictionary
和 HashTable
两类字典都支持外部传入 IEqualityComparer
接口实例以自定义 GetHashCode
、object.Equals
逻辑。 IEqualityComparer
接口定义如下:
public interface IEqualityComparer<in T> { bool Equals(T? x, T? y); int GetHashCode([DisallowNull] T obj); }
StringComparer.OrdinalIgnoreCase
是常见的 IEqualityComparer
实例之一:
var value = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
Notice
StringComparer
为IEqualityComparer
接口实例(它也实现了IComparer
),用于哈希表;StringComparison
为枚举,用于比较(Equals
方法)。
Notice
顺序比较器
IComparer
对于不排序的字典(如Dictionary
和Hashtable
)没有作用。
-
Dictionary
、Hashtable
等 不排序 字典需要 IEqualityComparer
来获取散列码,不需要 IComparer
获取顺序-
SortedDictionary
、SortedList
等 排序 集合需要 IComparer
获取顺序,不需要 IEqualityComparer
获取散列码。
7.5.4 OrderedDictionary
OrderedDictionary
是一种** 非泛型 字典,其元素保持了 原始顺序 。使用 OrderedDictionary
既可以根据 索引 访问元素,也可以根据 键 **来访问元素。
C7.0 核心技术指南 第7版.pdf - p372 - C7.0 核心技术指南 第 7 版-P372-20240213111624
OrderedDictionary
是 Hashtable
和 ArrayList
的组合。因此它具有 Hashtable
的所有功能,也有诸如 RemoveAt
以及整数索引器等功能。它的 Keys
和 Values
属性可以按照原始添加的顺序返回键或值。
7.5.5 ListDictionary
和 HybridDictionary
-
ListDictionary
内部通过 独立链表 实现,可以保持元素 原始顺序 ,不支持排序。在大型列表中表现较差,主要用于处理高效且非常小的列表(小于 10 个元素)。
-
HybridDictionary
ListDictionary
的变种,当数据达到一定量,内部实现从ListDictionary
自动转换为 Hastable
,存在转换的开销。
这两者类都只有非泛型形式,不如直接使用 Dictionary<TKey, TValue>
。
7.5.6 排序字典
支持键值排序的字典类有两个:
SortedDictionary |
SortedList |
|
---|---|---|
是否泛型 | 是 | 否 |
内部实现 | 红黑树 | 排序数组 |
检索速度 | 快 | 很快(二分查找) |
插入速度 | 快 | 慢 |
Keys 、Values 通过索引访问 |
不可以 | 可以 |
重复键值 | 不允许 | 不允许 |
总结
本节讲到的字典有:
-
Dictionary<TKey, TValue>
和Hashtable
-
OrderedDictionary
-
ListDictionary
和HybridDictionary
-
SortedDictionary
和SortedList
Dictionary | Hashtable | SortedDictionary | OrderedDictioanry | ListDictioanry | HybridDictionary | SortedList | |
---|---|---|---|---|---|---|---|
是否泛型 | 是 | 否 | 是 | 否 | 否 | 否 | 是 |
内部实现 | 散列表 | 散列表 | 红黑树 | ArrayList + Hashtable |
单向链表 | ListDictionary + Hashtable |
有序数组 |
通过索引访问 | 否 | 否 | 否 | 是 | 否 | 否 | Keys 、Values 可通过索引访问 |
是否保持原序 | 否 | 否 | 否 | 是 | 是 | 否 | 否 |
是否排序 | 否 | 否 | 是 | 否 | 否 | 否 | 是 |
特点 | 元素自动排序 | 数据保持原始顺序 | 数据量小于 10 | 元素自动排序,可以通过索引访问键值、Value 值。 |
7.6 自定义集合与代理
7.6.1 Collection<T>
和 CollectionBase
Collection<T>
是一个可定制的 List<T>
包装类。其内部定义了四个 虚方法 和一个 protected
属性 ,如下所示:
public class Collection<T> : IList<T>, ICollection<T>, IEnumerable<T>, IEnumerable, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>
{
// ...
protected IList<T> Items => items;
protected virtual void ClearItems();
protected virtual void InsertItem(int index, T item);
protected virtual void RemoveItem(int index);
protected virtual void SetItem(int index, T item);
}
我们可以通过覆写修改其内部行为,操作 Items
更改内部数据。
Warn
Collection<T>
也有一个可以接受现有IList<T>
的构造器,内部未对元素进行复制,因此外部可以对元素进行更改。
如下代码是对 Collection<T>
的简单应用:
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); }
}
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);
}
7.6.2 KeyedCollection<TKey, TValue>
和 DictionaryBase
Collection<T>
用于自定义集合,KeyedCollection<TKey, TValue>
用于自定义字典。它继承自 Collection<T>
,并重载了 索引器 ,可以通过 TKey
访问 TValue
。
KeyedCollection<TKey, TValue>
的实现同时使用了 List<T>
和 Dictionary<TKey, TValue>
。
相较 Collection<T>
,KeyedCollection<TKey, TValue>
定义了如下附加成员:
public abstract class KeyedCollection <TKey, TItem> : Collection <TItem>
{
// ...
protected abstract TKey GetKeyForItem(TItem item);
protected void ChangeItemKey(TItem item, TKey newKey);
public TItem this[TKey key] { get; }
protected IDictionary<TKey, TItem> Dictionary { get; }
}
注意:
KeyedCollection
的Add
方法由Collection<T>
实现,仅接受Value
值,插入元素时未指定Key
值。因此需要覆写GetKeyForItem
方法,KeyedCollection
的内部方法会调用它以获取Value
对应的Key
值。
7.6.3 ReadOnlyCollection<T>
ReadOnlyCollection<T>
是一个包装器(或称作代理),它用于为类提供公开的只读集合,而内部可以对集合进行更改。
只读集合的** 构造器 **接受另一个集合并维护一个输入集合的持久引用。它不会静态复制输入的集合,所以输入集合的后续修改都可以通过这个只读包装器显示出来。
集合的命名空间如下:
C7.0 核心技术指南 第7版.pdf - p339 - C7.0 核心技术指南 第 7 版-P339-20240211093853
7.7 扩展相等比较和排序操作
7.7.1 IEqualityComparer
和 EqualityComparer
相等比较器主要用于 Dictionary
和 Hashtable
类型,允许其使用非默认的相等比较和散列码计算行为。IEqualityComparer
接口定义如下:
public interface IEqualityComparer<in T>
{
bool Equals(T? x, T? y);
int GetHashCode([DisallowNull] T obj);
}
IEqualityComparer
接口有泛型、非泛型两种,我们可以通过 EqualityComparer
抽象类,仅需实现泛型方法,自动实现非泛型方法,一次性实现两个接口。该基类定义如下:
public abstract class EqualityComparer<T> : IEqualityComparer,
IEqualityComparer<T>
{
public abstract bool Equals (T x, T y);
public abstract int GetHashCode (T obj);
bool IEqualityComparer.Equals (object x, object y);
int IEqualityComparer.GetHashCode (object obj);
public static EqualityComparer<T> Default { get; }
}
如下为样例代码:
public class Customer
{
public string LastName;
public string FirstName;
public Customer (string last, string first)
{
LastName = last;
FirstName = first;
}
}
public class LastFirstEqComparer : EqualityComparer <Customer>
{
public override bool Equals (Customer x, Customer y)
=> x.LastName == y.LastName && x.FirstName == y.FirstName;
public override int GetHashCode (Customer obj)
=> (obj.LastName + ";" + obj.FirstName).GetHashCode();
}
void Main()
{
Customer c1 = new Customer ("Bloggs", "Joe");
Customer c2 = new Customer ("Bloggs", "Joe");
Console.WriteLine (c1 == c2); // False
Console.WriteLine (c1.Equals (c2)); // False
var d = new Dictionary<Customer, string>();
d [c1] = "Joe";
Console.WriteLine (d.ContainsKey (c2)); // False
var eqComparer = new LastFirstEqComparer();
d = new Dictionary<Customer, string> (eqComparer);
d [c1] = "Joe";
Console.WriteLine (d.ContainsKey (c2)); // True
}
EqualityComparer<T>.Defalut
EqualityComparer<T>.Default
属性会返回一个通用的相等比较器,替代 静 态的 object.Equals
方法。它会首先检查 T
是否实现了 IEquatable<T>
,实现了则直接调用实现类,从而避免装箱开销。这在泛型方法中尤其有用:
static bool Foo<T> (T x, T y)
{
bool same = EqualityComparer<T>.Default.Equals (x, y);
...
}
7.7.2 IComparer
和 Comparer
顺序比较器用自定义的排序逻辑替换** 排序字典 ** ** SortedDictionary
和 排序集合 ** ** SortedList
** 中的排序逻辑。
Notice
顺序比较器
IComparer
对于不排序的字典(如Dictionary
和Hashtable
)没有作用。
-
Dictionary
、Hashtable
等 不排序 字典需要 IEqualityComparer
来获取散列码,不需要 IComparer
获取顺序-
SortedDictionary
、SortedList
等 排序 集合需要 IComparer
获取顺序,不需要 IEqualityComparer
获取散列码。
以下是 IComparer
接口的定义:
public interface IComparer
{
int Compare(object? x, object? y);
}
public interface IComparer<in T>
{
int Compare(T? x, T? y);
}
以下是 Comparer<T>
的定义
public abstract class Comparer<T> : IComparer, IComparer<T>
{
public static Comparer<T> Default { get; }
public abstract int Compare(T? x, T? y);
int IComparer.Compare(object x, object y);
}
Comparer<T>
和 EqualityComparerIComparer<T>
接口,非泛型 IComparer
接口已自动实现。
以下是样例代码:
class Wish
{
public string Name;
public int Priority;
public Wish (string name, int priority)
{
Name = name;
Priority = priority;
}
}
class PriorityComparer : Comparer <Wish>
{
public override int Compare (Wish x, Wish y)
{
if (object.Equals (x, y)) return 0; // Fail-safe check
return x.Priority.CompareTo (y.Priority);
}
}
void Main()
{
var wishList = new List<Wish>();
wishList.Add (new Wish ("Peace", 2));
wishList.Add (new Wish ("Wealth", 3));
wishList.Add (new Wish ("Love", 2));
wishList.Add (new Wish ("3 more wishes", 1));
wishList.Sort (new PriorityComparer());
wishList.Dump();
}
Notice
实现
Compare
方法,要先进行 Equals
判断:public override int Compare (Wish x, Wish y) { if (object.Equals (x, y)) return 0; // Fail-safe check return x.Priority.CompareTo (y.Priority); }
7.7.3 StringComparer
StringComparer
是一个预定义的扩展类,它同时实现了 IEqualityComparer
和 IComparer
(及其泛型版本),所以它可以和任何类型的字典或排序集合一起使用。
StringComparer
是 抽象 类,具体的比较器实例通过它的静态方法和属性获得。例如:
-
StringComparer.Ordinal
(序列比较)是字符串** 相等 比较**的默认行为; -
StringComparer.CurrentCulture
(文化比较)是字符串** 顺序 比较**的默认行为。
其区别详见6.1.3.1 序列比较与文化相关的字符串比较
public abstract class StringComparer : IComparer, IComparer <string>,
IEqualityComparer,
IEqualityComparer <string>
{
public abstract int Compare (string x, string y);
public abstract bool Equals (string x, string y);
public abstract int GetHashCode (string obj);
public static StringComparer Create (CultureInfo culture,
bool ignoreCase);
public static StringComparer CurrentCulture { get; }
public static StringComparer CurrentCultureIgnoreCase { get; }
public static StringComparer InvariantCulture { get; }
public static StringComparer InvariantCultureIgnoreCase { get; }
public static StringComparer Ordinal { get; }
public static StringComparer OrdinalIgnoreCase { get; }
}
7.7.4 IStructuralEquatable
和 IStructualComparable
这两个接口用于内部元素结构化比较,Array
、Tuple
显式实现了这两个接口。接口定义如下:
public interface IStructuralEquatable
{
bool Equals (object other, IEqualityComparer comparer);
int GetHashCode (IEqualityComparer comparer);
}
public interface IStructuralComparable
{
int CompareTo (object other, IComparer comparer);
}
使用时,IEqualityComparer
/IComparer
会应用至组合对象的每一个元素。
如下将分别输出“ False 、 True ”:
int[] a1 = { 1, 2, 3 };
int[] a2 = { 1, 2, 3 };
IStructuralEquatable se1 = a1;
a1.Equals (a2).Dump();
se1.Equals (a2, EqualityComparer<int>.Default).Dump();
如下将分别输出“ True 、 0 ”:
var t1 = Tuple.Create (1, "foo");
var t2 = Tuple.Create (1, "FOO");
IStructuralEquatable se1 = t1;
se1.Equals (t2, StringComparer.InvariantCultureIgnoreCase).Dump();
IStructuralComparable sc1 = t1;
sc1.CompareTo (t2, StringComparer.InvariantCultureIgnoreCase).Dump();
标签:get,int,void,IEnumerable,集合,new,public From: https://www.cnblogs.com/hihaojie/p/18639810/chapter-7-collection-2n5qb5