• C# 迭代器


    IEnumerable接口允许使用foreach循环。在foreach循环中并不是只能使用集合类,相反,在foreach循环中使用定制类通常有很多优点。

      但是,重写使用foreach循环的方式或者提供定制的实现方式并不一定很简单。为了说明这一点,下面有必要深入研究一下foreach循环。在foreach循环中,迭代一个collectionObject集合的过程如下:

      (1)调用collectionObject.GetEnumerator(),返回一个IEnumerator引用,这个方法可通过IEnumerable接口的实现代码来获得,但这是可选的。

      (2)调用所返回的IEnumerator接口的MoveNext()方法。

      (3)如果MoveNext()方法返回true,就是用IEnumerator接口的Current属性来获取对象的一个引用,用于foreach循环。

      (4)重复前面两步,直到MoveNext()方法返回false为止,此时循环停止。

      所以,为在类中进行这些操作,必须重写几个方法,跟踪索引,维护Current属性,以及执行其他一些操作。这需要做许多的工作。

      一个较简单的替代方法是使用迭代器。使用迭代器将有效地自动生成许多代码,正确地完成所有任务。而且,使用迭代器的语法掌握起来非常容易。

      迭代器的定义是,它是一个代码块,按顺序提供了要在foreach块中使用的所有值。一般情况下,这个代码块是一个方法,但也可以使用属性访问器和其他代码块作为迭代器。这里为了简单起见,仅介绍方法。

      无论代码块是什么,其返回类型都是有限制的。与期望正好相反,这个返回类型与所美剧的对象类型不同。例如,在表示Animal对象集合的类中,迭代器块的返回类型不可能是Animal。两种可能的返回类型是前面提到的接口类型:IEnumerable和IEnumerator。使用这两种类型的场合是:

      如果要迭代一个类,则使用方法GetEnumerator(),其返回类型是IEnumerator。

      如果要迭代一个类成员,例如一个方法,则使用IEnumerable。

      在迭代器块中,使用yield关键字选择要在foreach循环中使用的值,其语法如下:

    yield return <value>;

      利用这个信息就足以建立一个非常简单的示例,如下所示:

    public static IEnumerable SimpleList(){   yield return "string 1";   yield return "string 2";   yield return "string 3";}static void Main(string[] args){   foreach(string item in SimpleList())      WriteLine(item);   ReadKey();}

      为此,静态方法SimpleList()就是迭代器块。它是一个方法,所以使用IEnumerable返回类型。SimpleList()使用yield关键字为使用它的foreach块提供了3个值,每个值都输出到屏幕上。

      迭代器返回的是object类型的值,因为object是所有类型的基类,所以可从yield语句返回任何类型。但编译器的智能化程度很高,所以我们可以把返回值解释为foreach循环需要的任何类型。这里代码需要string类型的值,而这正是我们要使用的值。如果修改一行yield代码,让它返回一个整数,就会在foreach循环中出现一个类型转换异常。

      对于迭代器,可以使用下面的语句中断将信息返回给foreach循环的过程:

    yield break;

      在遇到迭代器中的这个语句时,迭代器的处理会立即中断,使用该迭代器的foreach循环也一样。

    using System;using System.Collections;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace Ch11Ex03{    public class Primes    {        private long min;        private long max;        public Primes() : this(2, 100) { }        public Primes(long minimum, long maximum)        {            if (minimum < 2)                min = 2;            else                min = minimum;            max = maximum;        }        public IEnumerator GetEnumerator()        {            for (long possiblePrime = min; possiblePrime <= max; possiblePrime++)            {                bool isPrime = true;                for (long possibleFactor = 2; possibleFactor <=                   (long)Math.Floor(Math.Sqrt(possiblePrime)); possibleFactor++)                {                    long remainderAfterDivision = possiblePrime % possibleFactor;                    if (remainderAfterDivision == 0)                    {                        isPrime = false;                        break;                    }                }                if (isPrime)                {                    yield return possiblePrime;                }            }        }    }}​​​​​​
    using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using static System.Console; namespace Ch11Ex03{    class Program    {        static void Main(string[] args)        {            Primes primesFrom2To1000 = new Primes(2, 1000);            foreach (long i in primesFrom2To1000)                Write($"{i} ");            ReadKey();        }    }}

      这个示例中的类可以枚举上下限之间的素数集合。封装素数的类利用迭代器提供了这个功能。

      Primes的代码开始时比较简单,用两个字段来存储表示搜索范围的最大值和最小值,并使用构造函数设置这些值。注意,最小值是有限制的,它不能小于2,这很合理,因为2是最小的素数。相关的代码则全部放在方法GetEnumerator()中。该方法的签名满足迭代器块的规则,因为它返回的是IEnumerator类型。

      为提取上下限之间的素数,需要一次测试每个值,所以用一个for循环开始。由于我们不知道某个数是不是素数,因此先假定这个数是个素数,再看看它是否不是素数。为此,需要看看该数能否被2到概述平方根之间的所有数整除。如果能,则该数不是素数,于是测试下一个数。如果该数的确是素数,就用yield把它传给foreach循环。

      这段代码有一个有趣之处:如果把上下限设置为非常大的数,在执行应用程序时,就会发现,会一次显示一个结果,中间有暂停,而不是一次显示所有的结果。这说明,无论代码在yield调用之前是否会终止,迭代器代码都会一次返回一个结果。在后台,调用yield都会中断代码的执行,当请求另一个值时,也就是当使用迭代器的foreach循环开始一个新循环时,代码会恢复执行。

      前面曾提到,将介绍迭代器如何用于迭代存储在字典类型的集合中的对象,而不必处理DictionaryItem对象。下面是集合类Animals:

    public class Animals : DictionaryBase{   public void Add(string newID, Animal newAnimal) => Dictionary.Add(newID, newAnimal);   public void Remove(string animalID) => Dictionary.Remove(animalID);   public Animal this[string animalID]   {      get{ return (animal)Dictionary[animalID];}      set{ Dictionary[animalID] = value;}   }}

      可以在这段代码中添加如下的简单迭代器,以便执行预期的操作:

    public new IEnumerator GetEnumerator(){   foreach(object animal in Dictionary.Values)     yiled return (Animal)animal;}

      现在可以使用下面的代码来迭代集合中的Animal对象:​​​​​​​

    foreach (Animal myAnimal in animalCollection){  WriteLine($"New {myAnimal.ToString()} object added to " + $" custom collection, Name = {myAnimal.Name}");}
  • 相关阅读:
    依赖注入(六)
    代码随想录leetcode200题之图论
    单片机原理与应用以及C51编程技术——硬件体系结构梳理
    Docker+nginx在CVM的机器远程发布hellogin
    DDoS检测防御实现方案
    Python自然语言处理的力量:NLTK库介绍
    京东二面:Redis为什么快?我说Redis是纯内存访问的,然后他对我笑了笑。。。。。。
    java.io.EOFException:ZLIB输入流的意外结束 - 从HTTP读取
    计算机网络之数据链路层
    【shell】交互式自动化执行命令
  • 原文地址:https://blog.csdn.net/biyusr/article/details/125498971