本文共 2823 字,大约阅读时间需要 9 分钟。
在上一篇中,我们看了一下枚举器以及.NET如何使用foreach循环,我们看到了枚举器实际上是如何通过使用MoveNext方法和Current属性从一个状态转换到另一个状态的对象。
我们知道,如果我们想要创建一个自定义枚举器,我们将需要实现接口或它的 副本,这是状态发挥作用和状态机的地方。查看枚举器的Current属性是如果成为对象还是泛型类型,我们可以利用这个优势,并实现从计算、到枚举,甚至整个工作流程的各种算法。所有“魔法”实际上都发生在MoveNext方法中,我们可以在其中执行任何从一个状态转换到另一个状态所需的操作。
所有这些对于理解我们是否希望通过实现接口或者泛型版本来实现我们自己的集合是必不可少的,因为我们必须告诉我们的集合应该如何遍历它,这意味着返回一个枚举器并实现MoveNext方法,基本上我们需要实现一个枚举器,或者如果我们在自己的实现中有一个内部集合,那么只需传递它。
但在大多数情况下,.NET提供的泛型集合绰绰有余,除非我们想要创建一个非常专业的迭代器或结构,如图形和二叉树。
这就把我们带到了关于.NET编译器在幕后做什么以使我们的生活更轻松的主题,我知道我们已经走了很长一段路,但是要更好地理解它是如何组合在一起的(所做的是值得的)。
现在输入我们的客人,yield关键字。为此,我准备了一个示例,以便更好地可视化yield使用的一个方面
public IEnumerable 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关键字时,它会查看方法返回类型(在这种情况下,它是类型 int的IEnumerable),并在后台生成一个枚举器,因此这实际上会创建一个枚举整数的对象,因此 yield return组合是相当于枚举器的Current属性和方法的其余部分,直到满足另一个 yield ,这相当于 MoveNext方法。通过“另一个yield”,我的意思是,就像手动实现的枚举器一样,它将保留其当前状态并在下次遇到 yield时使用它 。所以在这种情况下,第一次时函数将返回1,第二次调用返回1,第三次调用返回2,依此类推,直到 yield超出范围或方法结束。
你可以在你的方法中拥有任意数量的yield语句,不需要将它放在循环中,并且它将始终从它执行的最后一个返回行继续,让我们看一个例子:
public IEnumerable GetSomeIntegers(){ yield return 1; yield return 2; yield return 3;}
此方法将返回1,然后在下一次调用时它将返回2,然后在下一次调用后它将返回3。
但是yield构造具有另一种形式,也就是yield break,它会告诉枚举器它已经到达其范围的末尾,以下是示范:
IEnumerableFoo (bool breakEarly){ yield return "One"; yield return "Two"; if (breakEarly) yield break; yield return "Three";}
此示例仅返回“One”和“Two”,如果breakEarly参数为true,则永远不会达到“Three” 。
所以你看,使用yield return和yield break,我们可以设计一个复杂的工作流程,而无需实现我们自己的任何枚举器,并轻松使用外部参数。
接下来,我将向您展示一个违反正常执行流程的示例,并展示了LINQ如何通过其扩展方法在幕后工作,以及如何编写枚举器。
static void Main(){ foreach (int fib in EvenNumbersOnly(Fibs(6))) { Console.WriteLine (fib); }} static IEnumerable 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 EvenNumbersOnly (IEnumerable sequence){ foreach (int x in sequence) if ((x % 2) == 0) yield return x;}
在这里,我们有一个枚举器组合的例子。乍一看,我们希望Fibs首先执行,但这是违反工作流逻辑的部分,程序将首先进入EvenNumberOnly方法,然后当它到达foreach内部时,它才会实际进入Fibs方法。然后它实际上将继续执行foreach直到它可以返回一个值,此时它会将它写入屏幕,然后该过程从它停止的地方再次开始,保持两个EvenNumberOnly和Fibs枚举器的状态直到Fibs完成,此时EvenNumberOnly也将完成。
这就是LINQ如何允许我们链接多个操作并“实时”处理大型数据集,而不是在每个步骤中遍历整个元素集合。使用这种技术,我们还可以处理来自Web服务的分页数据,而无需预先进行大量调用并将其存储在内存中。
尽管它是一个非常好用且有用的功能,但我们必须记住,该yield 构造有一些限制:
总之,我们看到了如何实现自定义枚举器,而不必经历制作我们自己的自定义类型的麻烦,以及我们如何利用它foreach来做更多的事情而不仅仅是迭代一组项目。
原文地址:
转载地址:http://wzzhj.baihongyu.com/