枚举器与迭代器

简介: 枚举器与迭代器

这次我们再次来讲解一下开发人员会用但不理解的C#中的知识,这篇文章我们讲解一下枚举器与迭代器的知识。


零、枚举器

什么是枚举器

枚举器(enumerator)是一个只读的作用于序列值的只能向前的游标,并且实现了 System.Collections.IEnumeratar 或者 System.Collections.Generic.IEnumeratar 接口的对象。通常来说任何一个包含名为 MoveNext 方法和名称为 Current 属性的对象,.NET都会将它作为枚举器对待。通过一个例子来看一下枚举器是怎么定义的:

class EnumeratorDemo : IEnumerator
{
    public object Current
    {
        get { return true; }
    }
    public bool MoveNext()
    {
        return false;
    }
    public void Reset()
    {
        //more code
    }
}

2.

可枚举对象(enumerator object)是一序列的逻辑表示,本身不是游标,但可以基于本身产生游标对象。如果要迭代可枚举对象,可以使用 foreach 语句。可枚举对象可以是实现了 Ienumerable 或 Ienumerable 的对象,也可以是具有名为 GetEnumerator 方法并且方法返回一个枚举器的对象。同样我们通过代码来看一下怎么定义可枚举对象:

class Enumerable : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        IEnumerable<string> myEnumerable = null;
        return myEnumerable.GetEnumerator();
    }
}

上述代码只是为了展现枚举器和可枚举对象的定义,实际开发过程中应该需要将对应的方法填充完成。下面我们通过实际代码来看一下枚举器和可枚举对象在开发中的使用:

static void Main(string[] args)
{
    using (var item = "abcdefg".GetEnumerator())
    {
        while (item.MoveNext())
        {
            var _char = item.Current;
            Console.WriteLine(_char);
        }
    }
    Console.Read();
}

我来讲解一下上述代码,字符串是可枚举对象,因此可以通过 GetEnumerator 方法获得枚举器,然后使用枚举器的 MoveNext 防火阀做 while 语句的执行条件,MoveNext 方法在存在下一个值得时候会返回 True ,当不存在的时候返回 False 。最后使用枚举器的 Current 属性获得游标指向的值,并打印出来。打印结果如下:

image.png

如果枚举器实现了 IDisposable 接口,这时 foreach 语句可以隐式的清理掉枚举对象。


3. 小知识

我们在C#中可以用一行代码完成可枚举对象的实例化和元素填充,这种方法叫做集合初始化器,代码如下:

List<string> strList = new List<string> { "张三", "李四", "王五", "赵六" };

上述代码会被编译器翻译成如下代码:

List<string> strList = new List<string>();
strList.Add("张三");
strList.Add("李四");
strList.Add("王五");
strList.Add("赵六");

前面的例子中要求可枚举对象实现了 System.Collections.IEnumerable 接口,并存在一个可以接受适当参数的 Add 方法。同理字典也可以通过集合初始化器进行对象初始化和元素填充。


一、迭代器

1.什么是迭代器

迭代器简化了对象间的通信,使得不关心序列的类型,而获得序列中的每个元素。C#中迭代器被 IEnumerator 和 IEnumerable 和其对应的泛型接口封装。一个类如果实现了 IEnumerable 接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。下面我们先通过例子来看一下迭代器的使用:

static void Main(string[] args)
{
    foreach (int item in demo(5))
    {
        Console.WriteLine(item);
    }
    Console.Read();
}
static IEnumerable<int> demo(int demoCount)
{
    int data = 0;
    for (int i = 0; i < demoCount; i++)
    {
        yield return data;
        data = data + 1;
    }
}

上述代码中用到了 yield return , yield return 语句的意思是请求从枚举器产生的下一个元素。每当遇到 yield 时控制权都会回归到调用者那里,但是被调用者的状态还会保持。这个状态的生命周期绑定到了枚举器上,当调用这完成枚举之后状态就被释放。


2.原理

编译器把地带方法转换成私有的,实现了 IEnumerable 或者 IEnumerator 的类。内部的逻辑被反转并被切分到编译器生成的枚举器类中的 MoveNext 方法和 Current 属性里。这就意味着当你调用迭代器方法时,实际上时对编译器生成的类进行实例化。


3.语义

迭代器含有一个或多个 yield 语句的方法、属性或者索引器。而且必须返回 IEnumerable 、IEnumerable 、 IEnumerator 或者 IEnumerator 其中的一个,迭代器会根据返回的接口类型选择不同的语义。下面根据上述描述来看一个例子:

static void Main()
{
  foreach(int item in demo)
  {
    Console.WriteLine(item);
  }
}
static IEnumerable<int> Foo()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

4. 迭代器中的 return
再迭代器中我们无法使用 return 跳出迭代器,只能使用 yield break 来跳出迭代器。我们来看一段代码:

static IEnumerable<int> demo()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield break;
    yield return 4;
}

上述代码中我们使用了 yield break 来跳出迭代器,yield break 后面的 yield return 4 将不再返回。

这里需要注意的是 yield return 语句不可以出现在 try…catch…finally 语句块中。但是如果没有 catch 语句块,只有 try…finally 语句块,则 yield return 可以出现在其中。在 try…finally 语句块中,当枚举器到达终点时或者被释放掉时,finally 语句块中的代码就会执行,如果执行了 yield return 那么 foreach 语句也会释放掉枚举器,然后执行 finally 中的代码。当我们显示使用枚举器时如果没有释放掉枚举,那么将不会执行 finally 中的代码,为了避免这种情况的出现我们可以使用 using 语句。

迭代器我们可以嵌套使用,我们看一下例子:

class Program
{
    static void Main(string[] args)
    {
        foreach (int fib in GetNum(Fibs(12)))
            Console.WriteLine(fib);
        Console.Read();
    }
    static IEnumerable<int> Fibs(int count)
    {
        int result = 0;
        for (int i = 0; i < count; i++)
        {
            yield return result;
            result = result + i;
        }
    }
    static IEnumerable<int> GetNum(IEnumerable<int> sequence)
    {
        foreach (int x in sequence)
        {
            yield return x;
        }
    }
}

上述例子中每个元素在 MoveNext 操作请求的时候才会被计算,也就是说 foreach (int fib in GetNum(Fibs(12))) 没执行一次循环每个元素值就会被计算一次。这里需要注意,一般来说迭代器都会结合 foreach 语句一起使用,每次循环完成后都必须显示的或隐式的调用 Dispose 方法来释放掉枚举器。


这篇文章基本上涵盖了迭代器和枚举器的所有内容,如果需要进一步学习迭代器与枚举器,需要自己动手实践一下。

um(Fibs(12)))** 没执行一次循环每个元素值就会被计算一次。这里需要注意,一般来说迭代器都会结合 foreach 语句一起使用,每次循环完成后都必须显示的或隐式的调用 Dispose 方法来释放掉枚举器。


这篇文章基本上涵盖了迭代器和枚举器的所有内容,如果需要进一步学习迭代器与枚举器,需要自己动手实践一下。


目录
相关文章
|
4月前
详细解读COM中集合和枚举器笔记(2)枚举器内部实现
详细解读COM中集合和枚举器笔记(2)枚举器内部实现
20 0
|
5月前
|
C++ 容器
C++:迭代器
C++:迭代器
62 0
|
5月前
|
算法 C++ 容器
c++迭代器介绍
C++中的迭代器是一种抽象的数据访问对象,它允许对容器中的元素进行遍历,而不必暴露底层数据结构的细节。迭代器提供了一种通用的方法来访问容器中的元素,无论容器的类型是什么。C++标准库中的许多容器(如vector、list、map等)都支持迭代器。
71 0
|
设计模式 开发框架 .NET
C#——迭代器
C#——迭代器
82 0
C#——迭代器
|
安全 Java 编译器
枚举使用、转数组、实现接口、枚举单例
枚举使用、转数组、实现接口、枚举单例
122 0
|
索引 容器
集合之迭代器
集合之迭代器
229 0
集合之迭代器
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ + “ 操作符重载 | 集合中的 “ - “ 操作符重载 | 代码示例 )
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ + “ 操作符重载 | 集合中的 “ - “ 操作符重载 | 代码示例 )
118 0
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ + “ 操作符重载 | 集合中的 “ - “ 操作符重载 | 代码示例 )
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ << “ 操作符重载 | 使用集合中的 “ << “ 操作符添加一个元素 | 使用集合中的 “ << “ 操作符添加一个集合 )
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ << “ 操作符重载 | 使用集合中的 “ << “ 操作符添加一个元素 | 使用集合中的 “ << “ 操作符添加一个集合 )
115 0
【Groovy】集合遍历 ( 操作符重载 | 集合中的 “ << “ 操作符重载 | 使用集合中的 “ << “ 操作符添加一个元素 | 使用集合中的 “ << “ 操作符添加一个集合 )
|
Sentinel Serverless 索引
迭代器
迭代器是一个可以记住遍历的位置的对象。 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。 一、NEXT 语法:next(iterator[, default]) 说明: 1、函数必须接收一个可迭代对象参数,每次调用的时候,返回可迭代对象的下一个元素。
1113 0