C#集合类型大揭秘

集合是.NET FCL(Framework Class Library)的重要组成部分,我们平常撸C#代码时免不了和集合打交道,FCL提供了丰富易用的集合类型,给我们撸码提供了极大的便利。正是因为这种与生俱来的便利性,使得我们对集合既熟悉又陌生。很多同学可能一直还是停留在使用的层面上,那么今天我们一起来深入学习一下C#语言中的各种集合。

首先我们看一下 FCL 给我们提供的集合接口:

mark

FCL提供了泛型非泛型两大类集合类型。因为非泛型集合装箱和拆箱带来的性能开销问题,和泛型集合相比,已经变得越来越鸡肋。所以我们也侧重于泛型集合的分析,但是两者差别不大。

IEnumerable和IEnumerator

mark

IEnumerable接口是所有集合类型的祖宗接口,其作用相当于Object类型之于其它类型。如果某个类型实现了IEnumerable接口,就意味着它可以被迭代访问,也就可以称之为集合类型(可枚举)。IEnumerable接口定义非常简单,只有一个GetEnumerator()方法用于获取IEnumerator类型的迭代器。

mark

我们可以将迭代器想象成数据库的游标,即序列(集合)中的某个位置,迭代器只能在序列(集合)中向前移动。每调用一次MoveNext(),如果序列(集合)中还有下一个元素,则迭代器移动到下一个元素;Current用于获取序列(集合)中的当前元素;因为迭代器调用一次代码只需要获取一个元素,这意味着我们需要确定访问到了序列(集合)中的哪个位置。Reset()用于重置这种状态,但是基本上不会使用Reset()重置状态。

同一个序列(集合)可能同时存在多个迭代器操作,相当于同时对一个集合进行多个遍历。这种情况下可能会出现迭代彼此交错。那么如何解决呢?

集合类不直接支持 IEnumerator
IEnumerator 接口。而是直接支持 IEnumerable接口,其唯一方法是 GetEnumerator,此方法用于返回支持 IEnumerator 的对象。每次调用GetEnumerator()方法时都需要创建一个新的对象,同时迭代器必须保存自身的状态,记录此时已经迭代到哪一个元素。这样迭代器就像是序列中的游标。可以有多个游标,移动其中任何一个都可以枚举集合,与其他迭代器互不影响。

foreach是怎么实现的?

for依赖对 Length 属性和索引运算符 ([]) 的支持。借助 Length 属性,C# 编译器可以使用 for 语句迭代数组中的每个元素。for适用于长度固定且始终支持索引运算符的数组,但并不是所有类型集合的元素数量都是已知的。此外,许多集合类(包括 Stack、Queue 和 Dictionary)都不支持按索引检索元素。因此,需要使用一种更为通用的方法来迭代元素集合。假设可以确定第一个、第二个和最后一个元素,那么就没有必要知道元素数量,也没有必要支持按索引检索元素。foreach在这种背景下应运而生。实际上,foreach内部使用迭代器的MoveNext和Current完成元素的遍历。

List<int> list = new List<int>();
List<int>.Enumerator enumerator = list.GetEnumerator();
try
{
    int number;
    while (enumerator.MoveNext())
    {
        number = enumerator.Current;
        Console.WriteLine(number);
    }
}
finally
{
    enumerator.Dispose();
}

实现自定义集合

我们可以自己实现IEnumerable接口和IEnumerator接口实现自定义集合。

实现自定义可枚举类型:

public class MySet : IEnumerable
{
    internal object[] values;

public MySet(object[] values)
    {
        this.values = values;
    }

public IEnumerator GetEnumerator()
    {
        return new MySetIterator(this);
    }
}

手写实现自定义迭代器:

public class MySetIterator : IEnumerator
{
    MySet set;
    /// <summary>
    /// 保存迭代到的位置
    /// </summary>
    int position;
    internal MySetIterator(MySet set)
    {
        this.set = set;
        position = -1;
    }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/ffbb9b0749e1e76a7b6d186e7cb0a321.html