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
语句。