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方法,该方法返回对象枚举器
示意图:
枚举器
实现了以下任意条件的都是枚举器:
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
把枚举器的位置重置为原始状态
实现了该接口我们也可以自己调用指定方法来实现枚举,但不推荐
使用枚举器的方式
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)
数组是可枚举类型
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
在IEnumerable基础上IEnumerable
Current
返回T类型元素值
是一个只读属性
Dispose
释放资源
新代码应使用泛型版本,维护C#2.0以前的代码才使用非泛型版本
示意图:
原理图:
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中可以读取该迭代变量,但不可以修改
迭代器(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类型的项
实例二:
迭代器块
迭代器块是一个或多个yield语句的代码块
迭代器合成序列(Composing Sequences)
可以将多个迭代器合成一个迭代器
迭代器块中的关键yield语句:
yield return语句指定序列中返回的下一项
yield break语句指定序列中没有项了。常用于提前返回
根据迭代器块的返回类型,我们可以产生 枚举器 或 可枚举类型
注意:
迭代器中不可以使用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
即会将其转为可迭代类型
迭代器可以包含在:方法、属性、索引器中
使用迭代器生成枚举器Enumerator
将方法的返回值设定为IEnumerator
使用迭代器生成可枚举类型Enumerable
需要手动调用可枚举类型的GetEnumerator方法
两种声明的使用区别
迭代器生成多个可枚举类型
迭代器作为属性成员
迭代器注意
迭代器需要System.Collections.Generic命名空间,要使用using引用它
迭代器在编译器生成的枚举器中,没有实现Reset方法,所以不可以调用Reset
在后台迭代器在编译器生成的枚举器类包含4个状态:
Before 首次调用MoveNext之前的状态
Running 调用MoveNext后进入这个状态
Supended 状态机等待下次调用MoveNext的状态
After 没有更多项可枚举
迭代器与本地方法(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;
本文来自博客园,作者:重庆熊猫,转载请注明原文链接:https://www.cnblogs.com/cqpanda/p/16691006.html