yield 关键字执行对列表、数组等集合的自定义迭代。
yield 关键字的两种形式
yield 关键字有两种形式:
yield return- 在每次迭代时返回一个表达式yield break- 终止迭代
Iterator 中的 yield return
yield 关键字在迭代器中使用方式如下:
yield return <expression>;
我们在迭代器中使用 yield return:
using System;
using System.Collections.Generic;
class Program
{
// define an iterator method
static IEnumerable<int> getNumber()
{
// create a list of integers
List<int> myList = new List<int> { -1, -4, 3, 5 };
foreach (var num in myList)
{
// returns positive number from myList
if (num >= 0)
{
yield return num;
// location of the code is preserved
// so on the next iteration getNumber() is executed from here
Console.WriteLine("....");
}
}
}
static void Main()
{
// display return values of getNumber()
foreach (var items in getNumber())
{
Console.WriteLine(items);
}
}
}
输出
3 .... 5 ....
在上面的示例中,我们在 Main() 中的 foreach 循环中调用了 getNumber() 方法。在 getNumber() 方法内部,我们使用了 yield return。
在这里,当 Main() 中的 foreach 调用 getNumber() 时,getNumber() 内部的代码会被执行,直到遇到 yield return。
当执行到 yield return 时,代码的当前位置会被保留,然后控制权返回给调用者(即 foreach 循环),并打印 **3**。
在 foreach 的下一次迭代中,getNumber() 方法会再次被调用。这次,getNumber() 将从之前保留的位置继续执行。这意味着:
Console.WriteLine("....");
Console.WriteLine("...."); 会被执行,然后 getNumber() 继续执行,直到遇到下一个 yield return,并且这个过程会一直持续到 myList 的迭代完成。
让我们通过一张图来讨论上述程序的运行机制。
yield return 的工作原理
1. foreach 调用 getNumber() - 首先,Main() 函数内的代码开始执行。在 Main() 中,我们有一个 foreach 循环,它调用 getNumber() 方法。
2. 在 getNumber() 内部,创建了 myList。
3. 然后,getNumber() 中的 foreach 循环开始迭代 myList。请注意这段代码:
if (num >= 0)
{
yield return num;
// location of the code is preserved
// so on the next iteration getNumber() is executed from here
Console.WriteLine("....");
}
当 num = **3** 时,num >= 0 返回 True,并且遇到了 yield return。此时会发生两个操作:
- 暂停
getNumber()并保留当前代码位置 - 将控制权返回给
foreach循环(调用者)
4. 控制权返回给调用者 - 在这里,返回的值 **3** 被打印出来,然后 foreach 开始下一次迭代。
在下一次迭代中,getNumber() 方法再次被调用。在此次调用中,getNumber() 将从保留的位置恢复执行。
这意味着 Console.WriteLine("...."); 会被执行。
现在,在 if 块中,num = 5,所以 num >= 0 为 True。再次遇到 yield return。
5. 控制权再次返回给调用者(foreach 循环内)。在这里,**5** 被打印出来,foreach 在下一次迭代中调用 getNumber()。
该方法从保留的位置恢复执行并打印 ....。由于 myList 中没有其他元素了,迭代停止。
常见问题
如果不使用 yield return,我们需要创建一个临时集合。例如:
using System;
using System.Collections.Generic;
class Program
{
// define an iterator method
static IEnumerable<int> getEven()
{
// create a list of integers
List<int> myList = new List<int>() { 1, 2, 3, 4, 5 };
// create an empty temporary list
List<int> tempList = new List<int>();
// iterate through myList
foreach (var i in myList)
{
if (i % 2 == 0)
{
// adds i to temporary list
tempList.Add(i);
}
}
return tempList;
}
static void Main()
{
// display return values of getEven()
foreach (var items in getEven())
{
Console.WriteLine(items);
}
}
}
输出
2 4
在这里,我们创建了一个临时列表 tempList,它存储了 myList 中的所有偶数。
创建临时列表会减慢计算速度,并在处理大量值时占用更多内存。
yield break
yield break 用于结束迭代器(列表、数组等)块。让我们看一个例子来更清楚地理解它。
using System;
using System.Collections.Generic;
class Program
{
// define an iterator method
static IEnumerable<string> getString()
{
// create a list of strings
List<string> myList = new List<string> { "Sun", "Mon", "Tue" };
foreach (var day in myList)
{
if (day == "Mon")
{
// terminates the iterator block after encountering "Mon"
yield break;
}
yield return day;
}
}
static void Main()
{
// display return values of getString()
foreach (var items in getString())
{
Console.WriteLine(items);
}
}
}
输出
Sun
在 getString() 方法中,请注意:
foreach (var day in myList)
{
if (day == "Mon")
{
// terminates the iterator block after encountering "Mon"
yield break;
}
yield return day;
}
这里,
"Sun"=="Mon"为False,因此if块未执行。程序执行yield return day并将"Sun"返回给getString()。"Mon" == "Mon为True,因此if块被执行。在这里,遇到了yield break,它结束了迭代器块,不返回任何内容。
基本上,如果遇到 yield break,则表示集合中已没有更多元素。
注意: yield break 与 break 语句不同,因为 break 语句在常规方法中终止最内层的循环,而 yield break 终止迭代器方法并将程序控制权转移给调用者。
yield break 的作用类似于不返回任何内容的 return 语句。