在上一篇文章中,我们看了一下枚举器以及.NET如何使用foreach循环,我们看到了枚举器实际上是如何通过使用MoveNext方法和Current属性从一个状态转换到另一个状态的对象。
我们知道,如果我们想要创建一个自定义枚举器,我们将需要实现IEnumerator接口或它的泛型 副本,这是状态发挥作用和状态机的地方。查看枚举器的Current属性是如果成为对象还是泛型类型,我们可以利用这个优势,并实现从计算、到枚举,甚至整个工作流程的各种算法。所有“魔法”实际上都发生在MoveNext方法中,我们可以在其中执行任何从一个状态转换到另一个状态所需的操作。
所有这些对于理解我们是否希望通过实现IEnumerable接口或者泛型IEnumerable版本来实现我们自己的集合是必不可少的,因为我们必须告诉我们的集合应该如何遍历它,这意味着返回一个枚举器并实现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,它会告诉枚举器它已经到达其范围的末尾,以下是示范:
IEnumerable Foo (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 构造有一些限制:
- 该yield关键字只能用于返回IEnumerable形式的方法。
- 该yield关键字不能被用在try- catch块(其理由是,当一个异常被抛出,则枚举变得无效并且它将被释放),但它可以被用在try- finally块。
- 该方法不能包含ref或out参数。
- 它不能用于unsafe块。
- 它不能用在anonymous方法中,如lambda表达式。
总之,我们看到了如何实现自定义枚举器,而不必经历制作我们自己的自定义类型的麻烦,以及我们如何利用它foreach来做更多的事情而不仅仅是迭代一组项目。
原文地址:https://www.codeproject.com/Articles/1266944/IEnumerable-and-State-Machines