C#基础系列(2)之迭代器

1 迭代器介绍

       在.NET中,迭代器模式被IEnumerator和IEnumerable及其对应的泛型接口所封装。如果一个类实现了IEnumerable接口,那么就能够被迭代;调用GetEnumerator方法将返回IEnumerator接口的实现,它就是迭代器本身。迭代器类似数据库中的游标,它是数据序列中的一个位置记录。迭代器只能向前移动,同一数据序列中可以有多个迭代器同时对数据进行操作。
       迭代器可用于逐步迭代集合,例如列表和数组。迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。通过 foreach 语句或 LINQ 查询从客户端代码中使用迭代器。

2 C#4.0迭代器接口具体实现

泛型实现:

using System.Runtime.CompilerServices;
namespace System.Collections.Generic
{
    public interface IEnumerable<out T> : IEnumerable
    {
        IEnumerator<T> GetEnumerator();
    }
}

非泛型实现:

using System.Runtime.InteropServices;
namespace System.Collections
{
    [ComVisible(true)]
    [Guid("496B0ABE-CDEE-11d3-88E8-00902754C43A")]
    public interface IEnumerable
    {
        [DispId(-4)]
        IEnumerator GetEnumerator();
    }
}

再来看下IEnumerator的实现,它主要包括三个方法:Current,MoveNext和Reset。

using System.Runtime.InteropServices;
namespace System.Collections
{
    //     支持对非泛型集合的简单迭代。
    [ComVisible(true)]
    [Guid("496B0ABF-CDEE-11d3-88E8-00902754C43A")]
    public interface IEnumerator
    {
        // 获取集合中的当前元素。
        object Current { get; }
        // 如果枚举数已成功地推进到下一个元素,则为 true;如果枚举数传递到集合的末尾,则为 false。
        bool MoveNext();
        //将枚举数设置为其初始位置,该位置位于集合中第一个元素之前。
        void Reset();
    }
}

因此,迭代器方法或 get 访问器的返回类型可以是 IEnumerable、IEnumerable< T >、IEnumerator 或 IEnumerator< T >

3 手写迭代器实现

为了加深对迭代器的理解,本文参考了yangecnu的博客(大家可以去看看大神的分享)。因此,自己也尝试手(zhao)写(ban)一个迭代器来感受一个最原始的气息。在这里, 假设我们需要实现一个基于环形缓冲的新的集合类型。我们将实现IEnumerable接口,使得用户能够很容易的利用该集合中的所有元素。我们的忽略其他细节,将注意力仅仅集中在如何实现迭代器上。集合将值存储在数组中,集合能够设置迭代的起始点,例如,假设集合有5个元素,你能够将起始点设为2,那么迭代输出为2,3,4,0,最后是1.代码示例如下:
迭代器实现类:

using System;
using System.Collections;
namespace TestIEnumerator
{
    class IterationSample : IEnumerable
    {
        public Object[] values;
        public Int32 startingPoint;
        public IterationSample(Object[] values, Int32 startingPoint)
        {
            this.values = values;
            this.startingPoint = startingPoint;
        }
        public IEnumerator GetEnumerator()
        {
            //throw new NotImplementedException();
            return new IterationSampleEnumerator(this);
        }
    }

    class IterationSampleEnumerator : IEnumerator
    {
        IterationSample parent;//迭代的对象  #1
        Int32 position;//当前游标的位置 #2
        internal IterationSampleEnumerator(IterationSample parent)
        {
            this.parent = parent;
            position = -1;// 数组元素下标从0开始,初始时默认当前游标设置为 -1,即在第一个元素之前, #3
        }

        public object Current
        {
            get
            {
                if (position == -1 || position == parent.values.Length)//第一个之前和最后一个自后的访问非法 #5
                {
                    throw new InvalidOperationException();
                }
                Int32 index = position + parent.startingPoint;//考虑自定义开始位置的情况  #6
                index = index % parent.values.Length;
                return parent.values[index];
            }
        }

        public bool MoveNext()
        {
            if (position != parent.values.Length) //判断当前位置是否为最后一个,如果不是游标自增 #4
            {
                position++;
            }
            return position < parent.values.Length;
        }

        public void Reset()
        {
            position = -1;//将游标重置为-1  #7
        }
    }
}

主程序测试类Program.cs:

using System;
namespace TestIEnumerator
{
    class Program
    {
        static void Main(string[] args)
        {
            object[] values = { "a", "b", "c", "d", "e" };
            //从数组指定的开始位置进行迭代
            IterationSample collection = new IterationSample(values, 3);
            foreach (object x in collection)
            {
                Console.Write(x + " ");
            }//print "d e a b c"
            Console.ReadKey();
        }
    }
}

要实现一个简单的迭代器需要手动写这么多的代码:需要记录迭代的原始集合#1,记录当前游标位置#2,返回元素时,根据当前游标和数组定义的起始位置设置定迭代器在数组中的位置#6。初始化时,将当前位置设定在第一个元素之前#3,当第一次调用迭代器时首先需要调用MoveNext,然后再调用Current属性。在游标自增时对当前位置进行条件判断#4,使得即使当第一次调用MoveNext时没有可返回的元素也不至于出错#5。重置迭代器时,我们将当前游标的位置还原到第一个元素之前#7。

4 通过yield语句实现迭代

4.1 实现返回值为IEnumerable类型的方法

通过迭代器方法来实现迭代,一般用于函数方法中,具体表现为:通过yield 关键字来实现返回值为IEnumerable类型的方法,示例代码如下:

using System;
using System.Collections.Generic;

namespace TestIEnumerator
{
    class Program
    {
        public static IEnumerable<int>EvenSequence(int firstNumber, int lastNumber)
        {
            // Yield even numbers in the range.  
            for (int number = firstNumber; number <= lastNumber; number++)
            {
                if (number % 2 == 0)
                {
                    //如果实现的是非泛型版本的接口,迭代块返的yield type是Object类型,
                    //否则返回的是相应的泛型类型。
                    //此处的返回值类型为int类型
                    yield return number;

        }

        static void Main(string[] args)
        {
            foreach (int number in EvenSequence(5, 18))
            {
                Console.Write(numbe           // Output: 6 8 10 12 14 16 18  
            Console.ReadKey();
        }
    }

4.2 实现返回值为IEnumerator类型的方法

       迭代的集合类DaysOfTheWeek 。首先,被迭代的集合类DaysOfTheWeek 继承IEnumerable接口。然后,实现该接口下的GetEnumerator方法。由此,即可实现迭代的目的,无需理会底层IEnumerator 接口的3个实现方法:Current()、MoveNext()和Reset()。示例代码如下:

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

namespace TestIEnumerator
{
    class Program
    {
        public class DaysOfTheWeek : IEnumerable
        {
            private string[] days = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };

            public IEnumerator GetEnumerator()
            {
                for (int index = 0; index < days.Length; index++)
                {
                    // Yield each day of the week.  
                    yield return days[index];
                }
            }
        }

        static void Main(string[] args)
        {
            DaysOfTheWeek days = new DaysOfTheWeek();

            foreach (string day in days)
            {
                Console.Write(day + " ");
            }
            // Output: Sun Mon Tue Wed Thu Fri Sat  
            Console.ReadKey();

        }
    }
}

五 小结

       迭代器的实现有3方式:1.自己手动实现;2.可以通过实现返回值IEnumerable类型的方法;3迭代集合类时,.通过实现IEnumerable接口下的GetEnumerator方法。下一篇将介绍泛型集合的迭代器使用,有兴趣的可以继续浏览一下。

参考文献

  1. https://www.cnblogs.com/yangecnu/archive/2012/03/17/2402432.html
  2. https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/iterators#BKMK_GenericList

猜你喜欢

转载自blog.csdn.net/qq_24642743/article/details/80275849
今日推荐