枚举器和迭代器

Posted JohnYang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了枚举器和迭代器相关的知识,希望对你有一定的参考价值。

枚举器和可枚举类型

枚举器是一个可以依次返回请求的数组中元素的类实例对象,“知道”项的次序并跟踪它在序列中的位置,然后返回请求的当前项。

对于有枚举器的类型而言,必须有一个方法来获取它。获取一个对象枚举器的方法是调用对象的GetEnumerator方法。实现GetEnumerator方法的类型叫做可枚举类型

    class Program
    {
        public interface IJohn
        {
            void MyMethod();
        }

        static void Main()
        {
            int[] test = { 1, 2, 3 };
            var a = test.GetEnumerator();
            Type t = a.GetType();
            Console.WriteLine($"{t.Name}");
            Console.WriteLine($"{typeof(IJohn).Name}");
            Console.WriteLine($"{typeof(IEnumerable).Name}");
        }
        
    }

IEnumerator接口

实现了IEnumerator接口的枚举器包含3个函数成员:Current,MoveNext以及Reset

  • Current是返回序列中当前位置项的属性
    • 它是只读属性
    • 返回object类型的引用,所以可以返回任何类型
  • MoveNext是把枚举器位置前进到集合中下一项的方法,返回布尔值,指示新的位置是有效位置还是已经超过了序列的尾部
    • 如果新的位置是有效的,方法返回true
    • 如果新的是位置是无效的,方法返回false
    • 枚举器的原始位置在序列中的第一项之前,是-1,因此MoveNext必须在第一次使用Current之前调用。
  • Reset是把位置重置为原始状态的方法
static void Main()
        {
            int[] MyArray = { 10, 11, 12, 13 };
            IEnumerator ie = MyArray.GetEnumerator();

            while (ie.MoveNext())
            {
                int i = (int)ie.Current;
                Console.WriteLine($"{i}");
            }
        }

IEnumerable接口

可枚举类是指实现了IEnumerable接口的类IEnumerable接口只有一个成员-----GetEnumerator方法,它返回对象的枚举器。

    class MyColors : IEnumerable
    {
        string[] Colors = { "Red", "Yellow", "Blue" };
        public IEnumerator GetEnumerator()
        {
            return new ColorEnumerator(Colors);
        }
    }
    class ColorEnumerator : IEnumerator
    {
        string[] _colors;
        int _position = -1;
        public ColorEnumerator(string[] theColors) //构造函数
        {
            _colors = new string[theColors.Length];
            for (int i = 0; i < theColors.Length; i++)
            {
                _colors[i] = theColors[i];
            }
        }
        public object Current   //实现Current
        {
            get
            {
                if (_position == -1)
                    throw new InvalidOperationException();
                if (_position >= _colors.Length)
                    throw new InvalidOperationException();
                return _colors[_position];
            }
        }
        public bool MoveNext() //实现MoveNext
        {
            if (_position < _colors.Length - 1)
            {
                _position++;
                return true;
            }
            else
            {
                return false;
            }
        }
        public void Reset()
        {
            _position = -1;
        }
    }
    class Program
    {
        public interface IJohn
        {
            void MyMethod();
        }

        static void Main()
        {
            var mc = new MyColors();
            foreach (var color in mc)
            {
                Console.WriteLine(color);
            }
        }
        
    }

泛型枚举接口

实际上,大多数情况下应该使用泛型版本IEnumeratble<T>IEnumerator<T>,两者的区别如下:

对于非泛型接口形式:

  • IEnumerable接口的GetEnumerator返回实现IEnumerator枚举器的实例
  • 实现IEnumerator的类实现了Current属性,它返回的是object,然后必须把它转化为实际类型的对象

对于泛型接口形式:

  • IEnumerable<T>接口的GetEnumerator方法返回实现IEnumerator<T>的枚举器类的实例
  • 实现IEnumerator<T>的类实现了Current属性,返回的是实际类型的对象,而不是object基类的引用

由此可见,非泛型接口的实现不是类型安全的,它们返回的是object类型的引用,然后必须转化为实际类型,而泛型接口的枚举器是类型安全的,它返回的实际类型的引用。

迭代器

迭代器实际是在类中的一种特殊的结构,它返回一个泛型枚举器或可枚举类型,yield return语句声明这是枚举中的下一项。

迭代器块是由一个或多个yield语句的代码块,它可以是:方法主体;访问器主体;运算符主体;

产生枚举器的迭代器:

public  IEnumerator<string> IteratorMethod()
{
    ...
        yield return ...;
}

产生可枚举类型的迭代器:

public IEnumerable<string> IteratorMethod()
{
    ....
        yield return ...;
}

使用迭代器来创建枚举器

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
    class MyClass1
    {
        public IEnumerator<string> GetEnumerator()
        {
            return BlackAndWhite();
        }
        public IEnumerator<string> BlackAndWhite()
        {
            yield return "Black";
            yield return "Gray";
            yield return "White";
        }
    }
    
    class Program
    {

        static void Main()
        {
            var mc = new MyClass1();
            foreach (var s in mc)
            {
                Console.WriteLine(s);
            }
            
        }
        
    }
}

上述代码,BlackAndWhite方法是一个迭代器块,可为Myclass1类产生并返回枚举器,MyClass1实现了GetEnumerator方法,这意味着它是可枚举类型,该方法调用了BlackAndWhite方法,并返回了BlackAndWhite返回的枚举器。

使用迭代器来创建可枚举类型

使用迭代器来创建可枚举类型意思是迭代器块返回的是可枚举类型,并不是说迭代器块所在的类因为有了迭代器块就变成了可枚举类型,迭代器块所在的类是不是可枚举类型,是由其实现没实现GetEnumerator方法决定的,如果实现了,则就是可枚举类型,这种情况下,GetEnumerator方法可调用迭代器产生的可枚举类型的GetEnumerator方法来返回枚举器。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
    class MyClass1
    {
        public IEnumerator<string> GetEnumerator()
        {
            return BlackAndWhite().GetEnumerator();
        }
        public IEnumerable<string> BlackAndWhite()
        {
            yield return "Black";
            yield return "Gray";
            yield return "White";
        }
    }
    
    class Program
    {

        static void Main()
        {
            var mc = new MyClass1();
            foreach (var s in mc)
            {
                Console.WriteLine(s);
            }
            
        }
        
    }
}

常见的迭代器模式

  • 在类中设置返回枚举器的迭代器时,就必须通过实现GetEnumerator让类可枚举,如果不实现该方法,则设置的枚举器就失去了意义,因为也不能通过调用该迭代器来实现遍历的目的(遍历的是可枚举类,而不是枚举器)
  • 如果在类中设置返回可枚举类型的迭代器,可以让类实现GetEnumerator让类枚举,或者不实现该方法,而让类不可枚举。
    • 如果实现了GetEnumerator,那么该方法必须调用迭代器返回的可枚举类型的GetEnumerator方法来返回枚举器。
    • 如果不实现GetEnumerator,仍然可以使用由迭代器返回的可枚举类,只需要调用迭代器方法,即可实现遍历。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
    class MyClass1
    {
        //public IEnumerator<string> GetEnumerator()
        //{
        //    return BlackAndWhite().GetEnumerator();
        //}
        public IEnumerable<string> BlackAndWhite()
        {
            yield return "Black";
            yield return "Gray";
            yield return "White";
        }
    }
    
    class Program
    {

        static void Main()
        {
            var mc = new MyClass1();
            foreach (var s in mc.BlackAndWhite())
            {
                Console.WriteLine(s);
            }
            
        }
        
    }
}

下面错误实例阐述的是:需要遍历的是可枚举类型,而不是枚举器。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
    class MyClass1
    {
        //public IEnumerator<string> GetEnumerator()
        //{
        //    return BlackAndWhite().GetEnumerator();
        //}
        public IEnumerable<string> BlackAndWhite()
        {
            yield return "Black";
            yield return "Gray";
            yield return "White";
        }
    }
    
    class Program
    {

        static void Main()
        {
            var mc = new MyClass1();
            foreach (var s in mc.BlackAndWhite().GetEnumerator())
            {
                Console.WriteLine(s);
            }
            
        }
        
    }
}

产生多个可枚举类型

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
class Spectrum
    {
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };
        public IEnumerable<string> UVtoIR()
        {
            for (int i = 0; i < colors.Length; i++)
            {
                yield return colors[i];
            }
        }
        public IEnumerable<string> IRtoUV()
        {
            for (int i = colors.Length - 1; i >= 0; i--)
            {
                yield return colors[i];
            }
        }
    }
    
    class Program
    {
      
        public static void Main()
        {
            var a = new Spectrum();

            foreach (var color in a.UVtoIR())
            {
                Console.Write($"{color}->");
              
            }
            Console.WriteLine();

            foreach (var color in a.IRtoUV())
            {
                Console.Write($"{color}->");

            }
            Console.WriteLine();
        }


            
        
        
    }
}

上述代码,Spectrum类本身并不是可枚举类型,但它定义了两个枚举类型迭代器,通过调用该迭代器,也可以完成不同的迭代遍历任务。

将迭代器作为属性

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp1
{ 
    class Spectrum
    {
        bool _listFromUVtoIR;
        string[] colors = { "violet", "blue", "cyan", "green", "yellow", "orange", "red" };

        public Spectrum(bool listFromUVtoIR)
        {
            _listFromUVtoIR = listFromUVtoIR;
        }
        public IEnumerator<string> GetEnumerator()
        {
            return _listFromUVtoIR
                ? UVtoIR
                : IRtoUV;
        }
        public IEnumerator<string> UVtoIR
        {
            get
            {
                for (int i = 0; i < colors.Length; i++)
                {
                    yield return colors[i];
                }
            }
        }
        public IEnumerator<string> IRtoUV
        {
            get
            {
                for (int i = colors.Length - 1; i >= 0; i--)
                {
                    yield return colors[i];
                }
            }
        }
    }
    
    class Program
    {

        static void Main()
        {
            var startUV = new Spectrum(true);
            var startIR = new Spectrum(false);

            foreach(string color in startUV)
            {
                Console.Write($"{color}->");
            }
            Console.WriteLine();

            foreach (string color in startIR)
            {
                Console.Write($"{color}->");
            }
            Console.WriteLine();
            
        }
        
    }
}

有关迭代器其他事项

  • 迭代器需要System.Collections.Generic命名空间
  • 在编译生成的枚举器中,Reset方法并没有实现,所以调用该方法,会抛出System.NotSupportedException异常。
  • 在后台,由编译器生成的枚举器类是包含4个状态的状态机:

##### 愿你一寸一寸地攻城略地,一点一点地焕然一新 #####

以上是关于枚举器和迭代器的主要内容,如果未能解决你的问题,请参考以下文章

枚举器和迭代器

C#知识点-枚举器和迭代器

设计模式 - 适配器模式(adapter pattern) 枚举器和迭代器 具体解释

Python核心技术与实战——十四|深入了解迭代器和生成器

Python核心深入理解迭代器和生成器

Shader2.0的顶点着色器和片段着色器