OneBigLoser
OneBigLoser
发布于 2024-07-24 / 40 阅读
0
0

枚举器(Enumerator)的学习

基本概念

迭代器(Iterator)是一种设计模式,允许你遍历集合中的元素而不暴露其底层结构。在C#中,迭代器是通过实现 IEnumerableIEnumerator 接口或者使用 yield 关键字来实现的。

用途

  • 遍历集合:迭代器用于遍历集合(如数组、列表等)中的元素。

  • 自定义集合遍历:可以自定义集合的遍历方式,而不影响集合的实现。

在游戏开发中的用途

  • 遍历游戏对象:遍历游戏中的所有对象,如敌人、道具、玩家等。

  • 路径查找:遍历路径点来实现路径查找算法。

  • 动画帧处理:遍历和处理动画帧。

  • 枚举器的工作原理

    • 初始状态:枚举器的位置在第一个元素之前,位置为 -1。

    • 调用 MoveNext:将枚举器移动到集合的下一个元素。如果枚举器在初始状态,则移动到第一个元素。

    • 访问 Current:返回当前元素的值。

    • 继续调用 MoveNext:直到到达集合的末尾,此时 MoveNext 返回 false

    为什么从 -1 开始?

    • 初始化:从 -1 开始可以确保在第一次调用 MoveNext 之前,枚举器不会指向集合中的任何元素。

    • 防止直接访问:避免在未调用 MoveNext 之前直接访问 Current 属性,因为此时 Current 的值未定义。

    • 一致性:与其他编程语言中的迭代器行为保持一致,使开发者更容易理解和使用。

    示例代码及解释

    1.实现一个自定义枚举器

    using System;
    using System.Collections;
    using System.Collections.Generic;
    
    public class CustomCollection : IEnumerable<int>
    {
        private int[] items = { 1, 2, 3, 4, 5 };
    
        public IEnumerator<int> GetEnumerator()
        {
            return new CustomEnumerator(items);
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    
        private class CustomEnumerator : IEnumerator<int>
        {
            private int[] _items;
            private int _position = -1;
    
            public CustomEnumerator(int[] items)
            {
                _items = items;
            }
    
            public bool MoveNext()
            {
                _position++;
                return (_position < _items.Length);
            }
    
            public void Reset()
            {
                _position = -1;
            }
    
            public int Current
            {
                get
                {
                    if (_position < 0 || _position >= _items.Length)
                        throw new InvalidOperationException();
                    return _items[_position];
                }
            }
    
            object IEnumerator.Current => Current;
    
            public void Dispose()
            {
                // 清理资源
            }
        }
    }
    
    class Program
    {
        static void Main()
        {
            CustomCollection collection = new CustomCollection();
            foreach (var item in collection)
            {
                Console.WriteLine(item);
            }
        }
    }
    

解释

  • CustomCollection 实现了 IEnumerable<int> 接口。

  • CustomEnumerator 实现了 IEnumerator<int> 接口。

  • _position 初始值为 -1,表示在第一个元素之前的位置。

  • MoveNext 方法将 _position 增加 1,并返回是否在集合范围内。

  • Current 属性返回当前位置的元素,如果当前位置不合法则抛出异常。

2.使用 yield return 语句:

public IEnumerable<int> GetPowersOfTwo(int n)
{
    int result = 1;
    for (int i = 0; i < n; i++)
    {
        yield return result;
        result *= 2;
    }
}

示例代码

public class GameLevel
{
    private List<string> enemies = new List<string> { "Orc", "Troll", "Goblin" };

    public IEnumerator<string> GetEnumerator()
    {
        foreach (string enemy in enemies)
        {
            yield return enemy;
        }
    }
}

public class Program
{
    public static void Main()
    {
        GameLevel level = new GameLevel();
        foreach (string enemy in level)
        {
            Console.WriteLine(enemy);
        }
    }
}

优点和缺点

优点

  • 一致性:与其他语言和框架的迭代器行为一致,便于理解和使用。

  • 安全性:确保在调用 MoveNext 之前不能访问 Current,避免访问未定义的值。

  • 初始化简单:从 -1 开始,使得第一次调用 MoveNext 就能正确定位到第一个元素。

缺点

  • 理解成本:对于初学者来说,理解为什么从 -1 开始可能有点困惑,需要额外的解释。

基础练习题

  1. 创建一个简单的自定义集合类,包含整数数组,实现 IEnumerable<int> 接口,并使用自定义枚举器遍历集合中的元素。

  2. 修改上述代码,使枚举器能够遍历一个字符串数组,打印出所有字符串。

进阶练习题

  1. 实现一个自定义链表类,并实现 IEnumerable<T> 接口,使用自定义枚举器遍历链表中的元素。

  2. 实现一个栈(Stack)类,并实现 IEnumerable<T> 接口,使用自定义枚举器遍历栈中的元素(从栈顶到栈底)。


评论