C# —— IEnumerable和状态机

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mzl87/article/details/84979146

在上一篇文章,我们看了一下枚举器以及.NET如何使用foreach循环,我们看到了枚举器实际上是如何通过使用MoveNext方法和Current属性从一个状态转换到另一个状态的对象。

我们知道,如果我们想要创建一个自定义枚举器,我们将需要实现IEnumerator接口或它的泛型 副本,这是状态发挥作用和状态机的地方。查看枚举器的Current属性是如果成为对象还是泛型类型,我们可以利用这个优势,并实现从计算、到枚举,甚至整个工作流程的各种算法。所有魔法实际上都发生在MoveNext方法中,我们可以在其中执行任何从一个状态转换到另一个状态所需的操作。

所有这些对于理解我们是否希望通过实现IEnumerable接口或者泛型IEnumerable版本来实现我们自己的集合是必不可少的,因为我们必须告诉我们的集合应该如何遍历它,这意味着返回一个枚举器并实现MoveNext方法,基本上我们需要实现一个枚举器,或者如果我们在自己的实现中有一个内部集合,那么只需传递它。

但在大多数情况下,.NET提供的泛型集合绰绰有余,除非我们想要创建一个非常专业的迭代器或结构,如图形和二叉树。

这就把我们带到了关于.NET编译器在幕后做什么以使我们的生活更轻松的主题,我知道我们已经走了很长一段路,但是要更好地理解它是如何组合在一起的(所做的是值得的)。

现在输入我们的客人,yield关键字。为此,我准备了一个示例,以便更好地可视化yield使用的一个方面

public IEnumerable<int> Fibs (int fibCount)
{
  for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
  {
    yield return prevFib;
    int newFib = prevFib+curFib;
    prevFib = curFib;
    curFib = newFib;
  }
}

我们这里有一个返回所有“ fibCount” 斐波那契数列的方法,请注意yield关键字。当.NET编译器遇到yield关键字时,它会查看方法返回类型(在这种情况下,它是类型  intIEnumerable),并在后台生成一个枚举器,因此这实际上会创建一个枚举整数的对象,因此  yield return组合是相当于枚举器的Current属性和方法的其余部分,直到满足另一个 yield ,这相当于  MoveNext方法。通过另一个yield,我的意思是,就像手动实现的枚举器一样,它将保留其当前状态并在下次遇到 yield时使用它 。所以在这种情况下,第一次时函数将返回1,第二次调用返回1,第三次调用返回2,依此类推,直到  yield超出范围或方法结束。

你可以在你的方法中拥有任意数量的yield语句,不需要将它放在循环中,并且它将始终从它执行的最后一个返回行继续,让我们看一个例子:

public IEnumerable<int> GetSomeIntegers()
{
  yield return 1;
  yield return 2;
  yield return 3;
}

此方法将返回1,然后在下一次调用时它将返回2,然后在下一次调用后它将返回3

但是yield构造具有另一种形式,也就是yield break,它会告诉枚举器它已经到达其范围的末尾,以下是示范:

IEnumerable<string> Foo (bool breakEarly)
{
  yield return "One";
  yield return "Two";

  if (breakEarly)
    yield break;

  yield return "Three";
}

此示例仅返回“One”“Two”,如果breakEarly参数为true,则永远不会达到“Three” 

扫描二维码关注公众号,回复: 4515256 查看本文章

所以你看,使用yield returnyield break,我们可以设计一个复杂的工作流程,而无需实现我们自己的任何枚举器,并轻松使用外部参数。

接下来,我将向您展示一个违反正常执行流程的示例,并展示了LINQ如何通过其扩展方法在幕后工作,以及如何编写枚举器。

static void Main()
{
  foreach (int fib in EvenNumbersOnly(Fibs(6)))
  {
    Console.WriteLine (fib);
  }
}
    
static IEnumerable<int> Fibs (int fibCount)
{
  for (int i = 0, prevFib = 1, curFib = 1; i < fibCount; i++)
  {
    yield return prevFib;
    int newFib = prevFib+curFib;
    prevFib = curFib;
    curFib = newFib;
  }
}

static IEnumerable<int> EvenNumbersOnly (IEnumerable<int> sequence)
{
  foreach (int x in sequence)
    if ((x % 2) == 0)
      yield return x;
}

在这里,我们有一个枚举器组合的例子。乍一看,我们希望Fibs首先执行,但这是违反工作流逻辑的部分,程序将首先进入EvenNumberOnly方法,然后当它到达foreach内部时,它才会实际进入Fibs方法。然后它实际上将继续执行foreach直到它可以返回一个值,此时它会将它写入屏幕,然后该过程从它停止的地方再次开始,保持两个EvenNumberOnlyFibs枚举器的状态直到Fibs完成,此时EvenNumberOnly也将完成。

这就是LINQ如何允许我们链接多个操作并实时处理大型数据集,而不是在每个步骤中遍历整个元素集合。使用这种技术,我们还可以处理来自Web服务的分页数据,而无需预先进行大量调用并将其存储在内存中。

尽管它是一个非常好用且有用的功能,但我们必须记住,该yield 构造有一些限制:

  • yield关键字只能用于返回IEnumerable形式的方法。
  • yield关键字不能被用在trycatch块(其理由是,当一个异常被抛出,则枚举变得无效并且它将被释放),但它可以被用在tryfinally块。
  • 该方法不能包含refout参数。
  • 它不能用于unsafe块。
  • 它不能用在anonymous方法中,如lambda表达式。

总之,我们看到了如何实现自定义枚举器,而不必经历制作我们自己的自定义类型的麻烦,以及我们如何利用它foreach来做更多的事情而不仅仅是迭代一组项目。

 

原文地址:https://www.codeproject.com/Articles/1266944/IEnumerable-and-State-Machines

猜你喜欢

转载自blog.csdn.net/mzl87/article/details/84979146