YUAN
C#迭代器.txt
Date: 2023/10/12Tags: 迭代器CSharp

迭代器是一种对象,可以视为是一个集合的书签或者游标,表示一个集合中的某个位置,用以遍历集合中的部分或全部元素,它提供了一个方法顺序访问一个集合中的各个元素,而不暴露其内部表示,而C#已经为我们提供了几个迭代器的接口

枚举接口

枚举接口IEnumerable和IEnumerator​​​是迭代器模式(iterator pattern)​在C#中的实现​​​。它们实现在集合上进行简单迭代的效果

IEnumerable接口

集合需要实现IEnumerable接口,才能被迭代,这个接口实现GetEnumerator方法,这个方法会返回一个IEnumerator对象,这就是我们的迭代器对象

IEnumerator接口

迭代器实现IEnumerator接口,这个接口有三个方法

  • MoveNext 用于判断受否达到了迭代的终点
  • Current 获取当前迭代的值
  • Reset 重置迭代器

通过自定义类来实现枚举接口的功能

集合类

public class GameEngineEnumerable : IEnumerable  
{  
    private string[] _gameEngines = new string[5] { "Unity", "Godot", "GameMakerStudio", "Cocos", "RPGMaker" };  
  
    public IEnumerator GetEnumerator()  
    {
          return new GameEngineEnumerator(_gameEngines);  
    }
}

迭代器类

public class GameEngineEnumerator : IEnumerator  
{  
    private string[] _gameEngines;  
    private int _position = -1; //索引  
  
    public GameEngineEnumerator(string[] gameEngines)  
    {       
	     _gameEngines = new string[gameEngines.Length];  
        for (int i = 0; i < gameEngines.Length; i++)  
        {  
              _gameEngines[i] = gameEngines[i];  
        }   
    }  
    public bool MoveNext()  
    {       
    }  
    public void Reset()  
    {    
    }  
    public object Current { get; }  
}

接下来实现 IEnumerator接口的三个方法

public bool MoveNext()  
{  
    if (_position < _gameEngines.Length)   
    {  
        _position++;  
        return true;  
    }   
     return false;  
}
public object Current  
{  
    get  
    {  
        //判断越界情况  
        if (_position >= _gameEngines.Length)  
        {            return null;  
        }  
        return _gameEngines[_position];  
    }}
public void Reset()  
{  
    _position = -1;  
}

接下来,我们在主函数中实现遍历

public static void Main(string[] args)  
{  
    GameEngineEnumerable gameEngine = new GameEngineEnumerable();  
    IEnumerator gameEngineEnumerator = gameEngine.GetEnumerator();  
    while (gameEngineEnumerator.MoveNext())  
    {
        Console.WriteLine(gameEngineEnumerator.Current);  
    }       
}

成功输出string数组里面的值

使用yield语句简化迭代

我们把包含 yield 语句的方法或属性称为迭代块 按照以上的方式实现迭代显然太过繁琐,C#为我们提供了yield语句简化了这个过程,我们可以使用yield语句提供下一个值(yield return)或者表示迭代结束(yield break)

现在,我们注释掉GameEngineEnumerator类,将GetEnumerator方法改为以下代码

public IEnumerator GetEnumerator()  
{  
    //return new GameEngineEnumerator(_gameEngines);  
    for (int i = 0; i < _gameEngines.Length; i++)  
    {
       yield return _gameEngines[i];  
    }
}

一样能实现相同的效果,但代码得到了极大的简化 而当我们执行yield break时,迭代器立即停止,MoveNext立即返回false,Current停留在最后一次保留的值上 在yield return执行完毕以后,函数并不会销毁,而是“休克”,等待返回值的IEnumerator执行下一次MoveNext(),yield return语句指定了枚举器中下一个可枚举项,迭代器在每次调用MoveNext函数时,会顺着上一次的枚举项(yield return)按照我们自己写的逻辑执行到下一个枚举项去

foreach

在C#中,我们可以使用foreach语句遍历可枚举集合,例如数组,哈希表,字典等,例如

foreach (var item in str)
{
    Console.WriteLine(item);
}

而事实上,foreach内部就是由迭代器实现的,运行时并不直接支持 foreach 语句,C# 编译器会转换代码(foreach也类似于一种语法糖) 以上为官方的foreach写法,下面我们手动实现一个民间foreach

void ForeachFunc(string str)  
{  
    IEnumerator e = str.GetEnumerator();  
    while(e.MoveNext())  
    {    
        Console.WriteLine(e.Current);  
    }
}

我们调用这个方法,会发现输出的结果是一样的,实际上,foreach的实现其实非常简单,就类似于上面的民间foreach,对集合调用GetEnumerator方法获取迭代器(string继承了IEnumerable接口),然后使用while循环,直到MoveNext方法返回false,而在循环的过程中,可以调用Current属性,获取迭代器当前所对应的值

Published: 2023/10/12