首页 > 其他分享 >第7章 集合

第7章 集合

时间:2024-12-29 23:30:30浏览次数:7  
标签:get int void IEnumerable 集合 new public

第7章 集合

7.1 枚举

下图展示了部分集合接口:

image

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>​ 就必须提供一个枚举器。可以采用如下三种方式实现:

  1. 如果这个类“包装”了任何一个集合,那么就返回所包装集合的枚举器
  2. 使用 yield return​ ​来进行迭代
  3. 实例化自己的 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>​ 为 双向链表 ,其结构如下:

image

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>​ 和 EqualityComparer 相似,为抽象基类。我们仅需实现 IComparer<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 ​会应用至组合对象的每一个元素。

如下将分别输出“ FalseTrue ”:

int[] a1 = { 1, 2, 3 };
int[] a2 = { 1, 2, 3 };
IStructuralEquatable se1 = a1;
a1.Equals (a2).Dump();
se1.Equals (a2, EqualityComparer<int>.Default).Dump();

如下将分别输出“ True0 ”:

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

相关文章

  • Wend看源码-Java-集合学习(Queue)
    概述   Wend看源码-Java-集合学习(List)-CSDN博客    Wend看源码-Java-集合学习(Set)-CSDN博客    在前两篇文章中,我们分别探讨了Java集合框架的父类以及List集合和Set集合的实现。接下来,本文将重点阐述Java中的Queue集合,包括其内部的数据结构以及核心方......
  • Java集合
    Java集合单列集合ListArrayListLinkedListVectorArrayList、LinkedList、Vector区别SetHashSetLinkedHashSet双列集合HashMap常用API方法哈希冲突HashMap特点TreeMap单列集合数据存储在一列,继承Collection接口CollectionList:存储列表数据ArrayList:底层数......
  • 学习012-02-03-14 How to: Reorder an Action Container‘s Actions Collection(如何:对
    Howto:ReorderanActionContainer’sActionsCollection(如何:对操作容器的操作集合进行重新排序)InanXAFapplicationUI,ActionsarelocatedwithinActionContainers.YoucanusetheActionBase.CategorypropertyandtheApplicationModel’sActionDesign......
  • UML之集合类型
    无论何时当我们要使用一个多值对象时,我们必须要清楚两个问题,一是这些值的顺序重要吗?二是允许重复值的存在吗?在编程语言中还会有其他的明确的信息,在UML中,只需明确这两个问题的答案即可确定对应的集合类型。1.SetSet是一个不允许存在重复值且未排序的集合。例如一个骑行活动中,有......
  • java面试题-集合篇
    Collection1.Collection有哪些类?Java集合框架中的Collection接口是所有集合类的基础接口,定义了一些基本的集合操作,如添加元素、删除元素、判断是否包含某个元素等。常见的集合类包括List、Set和Queue。ListList接口定义了按照索引访问和操作元素的方法。它允许元素重复,......
  • java 多线程处理list集合数据的实例应用
    众所周知创建线程的三种方式:继承Thread,重写run方法实现Runnable接口,重新run方法实现Callable接口,重写call方法下面使用Callable,来说一下为什么使用1.Thread类和Runnable接口都不允许声明检查型异常,也不能定义返回值。没有返回值这点稍微有点麻烦。不能声明抛出检查型异常则......
  • alpha 第九章 集合
    ArrayList<String>list=newArrayList<>();与ArrayList的区别: 同步性:Vector是同步的,支持多线程并发访问。而ArrayList是异步的,因而ArrayList中的对象是线程不安全的 效率:Vector的执行效率比ArrayList差 数据增长:添加新元素时,当数组容量不够时需要扩容时,Vector缺省......
  • 504 快速集合
    //504快速集合.cpp:此文件包含"main"函数。程序执行将在此处开始并结束。///**http://oj.daimayuan.top/course/22/problem/648数轴上有n个人,他们要选一个地点集合。第i个人初始在位置ai上,他的移速最大是bi单位/秒,请问最少需要花费多少秒,这n个人可以在某一个......
  • ffmpeg滤镜命令集合
    ffmpeg滤镜filter详解:https://zhuanlan.zhihu.com/p/519922289FFmpeg是一个强大的工具,用于处理、转换、编辑和播放各种音频和视频格式。它的滤镜系统允许用户对多媒体文件进行各种转换和处理。以下是一些常用的FFmpeg滤镜:scale-改变视频的尺寸。例如,将视频缩放到1280x......
  • mybatis映射集合,嵌套查询
    publicinterfacePhoneMapper{  List<String>queryPhoneNameByIds(StringuserId);}<selectid="queryPhoneNameByIds"resultType="String">  SELECTp.phone_name  FROMphonep  WHEREp.user_id=#{userId}</selec......