C# 中的 forEach 和 Enumerables
介绍
本指南围绕两个非常古老的编程概念展开,这两个概念如今已在大多数语言中实现。其中一个概念是生成器,另一个是迭代器。在本指南中,我们将研究这些想法是如何融入 C# 的,并研究如何利用它们为自己谋利,同时考虑到所有的利弊。
生成器
生成器通常是一个函数,或是用语言实现的一个特殊例程,用于控制循环的行为。生成器与返回数组的函数有着惊人的相似之处,因为它们有参数,可以调用来生成一系列项目。
假设我们有一个函数,每次返回大约一百万个整数列表。如果没有生成器,则需要在内存中建立列表,为所有值分配位置,然后将这些值传递到需要去的地方。如果您有资源,那很好;但是,人们总是努力为这类问题找到解决办法,因此生成器诞生了。生成器每次将产生一个项目,允许调用者立即开始处理第一个值。这需要更少的内存,并允许应用程序中更具表现力的控制流。此功能自语言 2.0 版起就已可用。
迭代器
迭代器的概念可以追溯到 1974 年的 CLU 编程语言。根据设计,迭代器应该允许程序员遍历特定的容器。迭代器最常用于列表。它们在 C# 中作为接口实现,并在实现中与容器本身紧密耦合。迭代器允许访问容器中保存的数据。
从 2.0 版开始,语言开始支持迭代器。在 C# 中,迭代器被称为枚举器,它们来自IEnumerator接口。基本上,此接口提供了一个MoveNext()方法,该方法查找下一个元素并检查是否到达容器或集合的末尾。它还有一个Current属性,允许访问当前指向的元素的值。还有一个Reset()方法,允许您将枚举器移回其初始位置。在检索第一个元素之前,需要调用MoveNext() ,因为枚举器指向第一个元素之前。如果调用实现IEnumerable接口的对象或集合的GetEnumerator()方法,则可以获取枚举器。
注意:大多数容器类都实现了该接口;但是foreach循环无法对这些类进行操作。
执行
首先我给大家展示一个示例代码。列表类来自System.Collections.Generic命名空间。
我们创建一个列表。
List<string> toBeIterated = new List<string>();
toBeIterated.Add("Programming");
toBeIterated.Add("is");
toBeIterated.Add("really");
toBeIterated.Add("fun");
toBeIterated.Add("if");
toBeIterated.Add("you");
toBeIterated.Add("do");
toBeIterated.Add("it");
toBeIterated.Add("right");
我们可以非常简单地将此列表转换为生成器。
IEnumerable<string> iEnumerableOftoBeIterated = (IEnumerable<string>)toBeIterated;
使用foreach循环将允许我们遍历列表。
foreach(string element in iEnumerableOftoBeIterated)
{
Console.WriteLine(element);
}
执行代码将给出以下结果。
Programming
is
really
fun
if
you
do
it
right
这就是生成器方法。现在让我们看看如何使用迭代器方法。
让我们转换我们的列表。
IEnumerator<string> iEnumeratorOftoBeIterated = toBeIterated.GetEnumerator();
如上所述,GetEnumerator()将起作用,因为来自Generic命名空间的List类实现了IEnumerator接口。但是,无法使用foreach 。为此,我们需要一个while循环。
while (iEnumeratorOftoBeIterated.MoveNext())
{
Console.WriteLine($"The current value is: {iEnumeratorOftoBeIterated.Current}");
}
执行上述代码将得到以下内容。
The current value is: Programming
The current value is: is
The current value is: really
The current value is: fun
The current value is: if
The current value is: you
The current value is: do
The current value is: it
The current value is: right
请注意,没有break语句,但它不会进入无限模式。
注意:这两种方法的目标都是相同的:允许程序员遍历容器,在我们的例子中是列表。但是,当我们使用IEnumerator方法时,它只有在调用MoveNext()函数时才有效。IEnumerable和IEnumerator之间的主要区别在于后者保留了游标的当前状态。
一句忠告:如果您想要按顺序浏览集合,则应使用IEnumerable接口。如果您想要保留光标位置并在函数之间传递它,则应使用IENumerator。
让我们看一个实际的例子。我们想创建一个小应用程序,可以根据数字是奇数还是偶数来以不同的方式处理数字。IEnumerator为我们提供了一个优雅的解决方案,它涉及两个静态方法。
using System;
using System.Threading;
using System.Collections.Generic;
namespace generatorsNIterators
{
class Program
{
static void EvenProcessor(IEnumerator<int> i)
{
if (i.Current == 0)
{
Console.WriteLine("The list was processed, exiting!");
Thread.Sleep(10);
i.Dispose();
}
else if (i.Current % 2 != 0)
{
Console.WriteLine("The number is ODD, calling processor.");
OddProcessor(i);
}
else if(i.Current % 2 == 0)
{
Console.WriteLine($"Processing even: {i.Current}");
Console.WriteLine("Even number processed!");
i.MoveNext();
EvenProcessor(i);
}
else
{ i.Dispose(); }
}
static void OddProcessor(IEnumerator<int> i)
{
if (i.Current == 0)
{
Console.WriteLine("The list was processed, exiting!");
Thread.Sleep(5);
i.Dispose();
}
else if (i.Current % 2 == 0)
{
Console.WriteLine("The number is EVEN, calling processor.");
EvenProcessor(i);
}
else if(i.Current % 2 != 0)
{
Console.WriteLine($"Processing odd: {i.Current}");
Console.WriteLine("Odd number processed!");
i.MoveNext();
OddProcessor(i);
}
else
{ i.Dispose(); }
}
static void Main(string[] args)
{
List<int> myList = new List<int>(10);
for (int i = 1; i <= 20; i++) { myList.Add(i); }
IEnumerator<int> myListEnum = myList.GetEnumerator();
myListEnum.MoveNext();
OddProcessor(myListEnum);
Console.ReadKey();
}
}
}
当我们执行此代码时,控制台上会出现以下内容。
Processing odd: 1
Odd number processed!
The number is EVEN, calling processor.
Processing even: 2
Even number processed!
The number is ODD, calling processor.
Processing odd: 3
Odd number processed!
The number is EVEN, calling processor.
Processing even: 4
Even number processed!
The number is ODD, calling processor.
Processing odd: 5
Odd number processed!
The number is EVEN, calling processor.
Processing even: 6
Even number processed!
The number is ODD, calling processor.
Processing odd: 7
Odd number processed!
The number is EVEN, calling processor.
Processing even: 8
Even number processed!
The number is ODD, calling processor.
Processing odd: 9
Odd number processed!
The number is EVEN, calling processor.
Processing even: 10
Even number processed!
The list was processed, exiting!
这里发生了什么?我们有一个从 1 到 20 的数字列表,并且使用以下语句将此列表转换为迭代器。
IEnumerator<int> myListEnum = myList.GetEnumerator();
这是必要的,因为我们有两个静态方法,EvenProcessor和OddProcessor。两者都采用IEnumerator参数,即光标的当前位置。此解决方案不使用任何循环。它只是通过迭代器的魔力来工作。我们的列表只有 10 个项目,如果数字是偶数,则相应的函数将对其进行处理。奇数的情况也是如此。这里的关键是 i.Current以及光标位置保持不变的事实。
结论
总而言之,在我看来,生成器和迭代器非常有用。它们允许您在应用程序中创建干净的控制流,同时保持代码干净。在本指南中,我们定义了生成器和迭代器,探索了它们的来源和原因。然后我们积累了知识,最后进行了完整的演示。最酷的部分是我们能够处理列表而无需使用for、while或do while循环。我希望这篇文章对您有所帮助,并且您找到了想要的内容。如果您喜欢本指南,请点赞并继续关注更多内容,或者查看我的其他指南。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~