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}");
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值