重庆熊猫 Loading

C#教程 - 枚举器 & 迭代器(IEnumrator & Iterator)

更新记录
转载请注明出处:https://www.cnblogs.com/cqpanda/p/16691006.html
2022年9月19日 发布。
2022年9月10日 从笔记迁移到博客。

枚举器(Enumerator)

枚举器和可枚举类型作用

提供一个统一的接口对数据集合进行访问

不需要知道集合长度遍历数据

可枚举类型

实现了以下任意条件的都是可枚举类型:

Implements System.Collections.IEnumerable

Implements System.Collections.Generic.IEnumerable

Has a public parameterless method named GetEnumerator that returns an enumerator

实例:

class Enumerable // Typically implements IEnumerable or IEnumerable<T>
{
  public Enumerator GetEnumerator() {...}
}

IEnumerable接口

可枚举类是指实现了IEnumerable接口的类

IEnumerable接口只有一个成员,GetEnumerator方法,该方法返回对象枚举器

image

示意图:
image

枚举器

实现了以下任意条件的都是枚举器:

​ Implements System.Collections.IEnumerator

Implements System.Collections.Generic.IEnumerator

Has a public parameterless method named MoveNext and property called Current

实例:

class Enumerator // Typically implements IEnumerator or IEnumerator<T>
{
  public IteratorVariableType Current { get {...} }
  public bool MoveNext() {...}
}

IEnumerator接口

IEnumerator接口包含三个函数成员:

​ Current

返回当前序列中的当前位置项的属性

返回的值是只读的

返回object类型的引用,所以可以返回任何类型

​ MoveNext

​ 把枚举器的引用位置移动到下一项

​ 返回一个布尔值,指示到达新闻纸还是已经超过尾部

​ 如果位置有效,返回true,位置无效返回false

​ 第一次调用枚举器时必须先调用该方法,再调用Current

​ Reset

​ 把枚举器的位置重置为原始状态

image

实现了该接口我们也可以自己调用指定方法来实现枚举,但不推荐
image

使用枚举器的方式

high-level way

foreach (char c in "beer")
Console.WriteLine(c);

注意:

If the enumerator implements IDisposable, the foreach statement also acts as a using statement, implicitly disposing the enumerator object.

low-level way

using (var enumerator = "beer".GetEnumerator())
  while (enumerator.MoveNext())
  {
    var element = enumerator.Current;
    Console.WriteLine (element);
  }

枚举器和可枚举类型

使用foreach语句时,需要提供一个枚举器(enumerator)对象

枚举器按要求返回指定元素

获得枚举器的方法是调用GetEnumerator方法

实现了GetEnumerator方法的类型叫做可枚举类型(enumerable type)或可枚举(enumerable)

数组是可枚举类型

image

IEnumerable接口和IEnumerator接口实例

IEnumerator定义在类外部实例

using System;
using System.Collections;
using System.Collections.Generic;

namespace Test
{
    class Panda: IEnumerable
    {
        private int[] _innerArray = new int[] { 1, 2, 3, 4, 5 };
        public IEnumerator GetEnumerator()
        {
            return new PandaIEumerator(this._innerArray);
        }
    }

    class PandaIEumerator: IEnumerator
    {
        private int[] _innerData;
        private int position = -1;

        public PandaIEumerator(int[] arrayData)
        {
            this._innerData = arrayData;
        }

        public object Current
        {
            get
            {
                return this._innerData[this.position];
            }
        }

        public bool MoveNext()
        {
            ++this.position;
            if(this.position < this._innerData.Length)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Reset()
        {
            this.position = -1;
        }
    }

    class Test
    {
        static void Main()
        {
            Panda obj = new Panda();

            foreach (int item in obj)
            {
                Console.WriteLine(item);
            }
            
            Console.ReadKey();
        }
    }
}

IEnumerator定义在类内部实例

using System;
using System.Collections;

namespace PandaNamespace
{
    class PandaCollections: IEnumerable
    {
        private int[] data;

        public PandaCollections()
        {
            this.data = new int[] { 1, 2, 3, 4 };
        }

        public IEnumerator GetEnumerator()
        {
            return new PandaEnumerator(this.data);
        }

        private class PandaEnumerator:IEnumerator
        {
            private int[] data;

            private int position = -1;
            public PandaEnumerator(int[] data)
            {
                this.data = data;
            }

            public bool MoveNext()
            {
                if(this.position < this.data.Length-1)
                {
                    this.position++;
                    return true;
                }
                else
                {
                    return false;
                }
            }

            public object Current
            {
                get
                {
                    return this.data[this.position];
                }
            }

            public void Reset()
            {
                this.position = -1;
            }
        }
    }

    class PandaClass
    {
        static void Main(string[] args)
        {
            PandaCollections testObject = new PandaCollections();
            foreach (int item in testObject)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine("Task Process Over");
            Console.ReadKey();
        }
    }
}

IEnumerable接口 和 IEnumerator接口

IEnumerable接口的泛型版本

IEnumerator接口的泛型版本

IEnumerable接口一般配和IEnumerator接口使用,而IEnumerator的Current方法每次返回的都是Object的引用,使用时存在拆箱的运行消耗,并且存在类型不安全问题

IEnumerable接口一般配合IEnumerator接口使用,而IEnumerator的Current方法每次返回都是实际类型的引用,不存在拆箱的运行消耗,并且类型安全

在IEnumerable基础上IEnumerable拥有额外的一些方法:

Current

返回T类型元素值

是一个只读属性

Dispose

​ 释放资源

新代码应使用泛型版本,维护C#2.0以前的代码才使用非泛型版本

示意图:
image

原理图:
image

IEnumerable接口和IEnumerator接口实例

using System;
using System.Collections;
using System.Collections.Generic;

namespace Test
{
    class Panda: IEnumerable<int>
    {
        private int[] _innerArray = new int[] { 1, 2, 3, 4, 5 };

        IEnumerator<int> IEnumerable<int>.GetEnumerator()
        {
            return new PandaIEumerator(this._innerArray);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }
    }

    class PandaIEumerator: IEnumerator<int>
    {
        private int[] _innerData;
        private int position = -1;

        public PandaIEumerator(int[] arrayData)
        {
            this._innerData = arrayData;
        }

        public int Current
        {
            get
            {
                return this._innerData[this.position];
            }
        }

        object IEnumerator.Current => throw new NotImplementedException();

        public void Dispose()
        {
            
        }

        public bool MoveNext()
        {
            ++this.position;
            if(this.position < this._innerData.Length)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public void Reset()
        {
            this.position = -1;
        }
    }

    class Test
    {
        static void Main()
        {
            Panda obj = new Panda();

            foreach (int item in obj)
            {
                Console.WriteLine(item);
            }
            
            Console.ReadKey();
        }
    }
}

foreach循环

foreach语句是enumerator的消费者

注意:只有可枚举对象才可以使用foreach

注意:如果枚举器实现了IDisposable接口,foreach会自动调用dispose方法

foreach循环执行步骤如下:

​ 通过调用可枚举对象的GetEnumerator方法来获得对象的枚举器

​ 从枚举器依次获得迭代变量,注意foreach中可以读取该迭代变量,但不可以修改

image

迭代器(Iterator)

说明

C#2.0开始,我们可以使用迭代器(iterator)

编译器自动把我们定义的迭代器生成 可枚举类型 或 枚举器

迭代器需要System.Collections.Generic命名空间,要使用using引用它

迭代器的返回类型只可能是以下四种类型:

System.Collection.IEnumerable
System.Collection.Generic.IEnumerable<T>
System.Collection.IEnumerator
System.Collection.Generic.IEnumerator<T>

声明迭代器

迭代器返回一个泛型枚举器

yield return语句声明这是枚举中的下一项

枚举器不会一次返回所有元素,每次访问Current属性返回一个新值

iterator可以在method, property, indexer内实现

实例一:枚举器返回3个string类型的项
image

实例二:
image

迭代器块

迭代器块是一个或多个yield语句的代码块

迭代器合成序列(Composing Sequences)

可以将多个迭代器合成一个迭代器

迭代器块中的关键yield语句:
yield return语句指定序列中返回的下一项

​ yield break语句指定序列中没有项了。常用于提前返回

根据迭代器块的返回类型,我们可以产生 枚举器 或 可枚举类型

image

注意:

迭代器中不可以使用return

​ yield return不可以放在try-catch-finally语句中,但可以放在try-finally语句中

原因是最终迭代器会被转为类的MoveNext, Current, Dispose方法

​ 最好使用using来处理迭代器错误,而不是try-catch

​ 迭代器可以返回其他可枚举类型,形成迭代器组合序列(Composing Sequences)

实例:

IEnumerable<string> Foo()
{
    try { yield return "One"; } // 错误
    catch { ... }
}

实例:

IEnumerable<string> Foo()
{
    try { yield return "One"; } // 可以
    finally { ... }
}

实例:Multiple yield statements

static IEnumerable<string> Foo()
{
    yield return "One";
    yield return "Two";
    yield return "Three";
}

实例:使用yield break

static IEnumerable<string> Foo (bool breakEarly)
{
    yield return "One";
    yield return "Two";
    if (breakEarly)
    {
        yield break;
    }
    yield return "Three";
}

迭代器的返回类型

// Enumerable interfaces
System.Collections.IEnumerable
System.Collections.Generic.IEnumerable<T>
// Enumerator interfaces
System.Collections.IEnumerator
System.Collections.Generic.IEnumerator<T>

迭代器本质

编译器将迭代器转换为私有类,该类实现IEnumerable 或者 IEnumerator的方法

即会将其转为可迭代类型

迭代器可以包含在:方法、属性、索引器中

使用迭代器生成枚举器Enumerator

将方法的返回值设定为IEnumerator即可

image

使用迭代器生成可枚举类型Enumerable

需要手动调用可枚举类型的GetEnumerator方法

image

两种声明的使用区别

image

迭代器生成多个可枚举类型

image

迭代器作为属性成员

image

迭代器注意

迭代器需要System.Collections.Generic命名空间,要使用using引用它

迭代器在编译器生成的枚举器中,没有实现Reset方法,所以不可以调用Reset

在后台迭代器在编译器生成的枚举器类包含4个状态:
Before 首次调用MoveNext之前的状态

​ Running 调用MoveNext后进入这个状态

​ Supended 状态机等待下次调用MoveNext的状态

​ After 没有更多项可枚举

image

迭代器与本地方法(Iterators with methods)

Iterators can be local methods

迭代器与异常处理

yield return语句不可以出现在try/catch/finally语句块中

原因是:迭代器在编译阶段会转为私有类型放在这些语句块中会导致异常复杂

IEnumerable<string> Foo()
{
  try { yield return "One"; }    // Illegal
  catch { ... }
}

但是可以放在try/finally语句块中

IEnumerable<string> Foo()
{
  try { yield return "One"; }    // OK
  finally { ... }
}

使用迭代器生成属性

class PandaTestClass
{
    public IEnumerable<int> SomeProp 
    { 
        get {
            yield return 666;
            yield return 888;
            yield return 999;
        } 
}

    public IEnumerator<int> SomeProp2
    {
        get
        {
            yield return 666;
            yield return 888;
            yield return 999;
        }
    }
}

迭代器yield语句的限制

yield表达式只可以放在方法、自定义操作符、索引或属性的get方法中

并且这些方法城的参数不可以有ref或者out修饰

yield语句不可以放置在匿名方法和Lambda表达式中

yield语句可能不会出现在try语句的catch和finally子句中

只有在没有catch块的情况下,yield语句才可能出现在try块中

代码优化

提前释放资源,减少内存占用

使用using语句可以提前释放枚举器和迭代器

常用于枚举器和迭代器占用非常多资源的情况

string firstElement = null;
var sequence = Foo();
using (var enumerator = sequence.GetEnumerator())
  if (enumerator.MoveNext())
    firstElement = enumerator.Current;
posted @ 2022-09-19 08:54  重庆熊猫  阅读(677)  评论(0编辑  收藏  举报