考虑以下方法:
IEnumerable<DateTime> GetTimes(int count)
{
for (int i = 0; i < count; i++)
yield return DateTime.Now;
yield break;
}
现在,我想称之为:
var times = GetTimes(2);
Console.WriteLine("First element:" + times.Take(1).Single().ToString());
Console.WriteLine("Second element:" + times.Skip(1).Take(1).Single().ToString());
Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString());
Console.WriteLine("Finished...");
但最后一行代码永远不会运行。为什么呢?
答案 0 :(得分:2)
由于interators的工作方式,行yield break;
永远不会运行。
执行
时不执行迭代器方法GetTimes(int count)
var times = GetTimes(2);
相反,它会在您从中提取值时执行(例如,当您执行times.Take(1).Single().ToString()
时)。
有两件事情会产生这种看似奇怪的行为:
只要遇到yield return
行,迭代器就会停止。当您尝试从中获取另一个元素时,迭代器执行从离开的位置继续执行。如果不这样做,它将永远不会恢复执行。
您实际上正在执行两次迭代器。你做的事情通常被称为"multiple enumeration of IEnumerable"
为了说明幕后实际发生的事情,让我们对GetTimes
方法进行一些小改动:让我们每次都不返回相同的日期,但每次调用时我们都会返回上一个日期+ 1天。我们还添加一些Console.WriteLine
来跟踪执行。所以,新方法体可能如下所示:
IEnumerable<DateTime> GetTimes(int count)
{
for (int i = 0; i < count; i++)
{
Console.WriteLine("returning the value with index " + i);
yield return DateTime.Now.AddDays(i);
}
Console.WriteLine("About to hit the `yield break`! Awesome!");
yield break;
}
现在运行代码会产生以下输出:
returning the value with index 0 First element:11/16/2012 11:34:46 PM returning the value with index 0 returning the value with index 1 Second element:11/17/2012 11:34:46 PM Finished...
这说明了以上两点:
GetTimes
一旦返回值就会停止执行,只要请求另一个值,它就会从同一状态恢复。
Iterator执行两次(第二次使用times
时Skip
请求一个值,Take
请求下一个值并使用它。)
好的,但为什么yield break;
没有被执行?这是因为你的迭代器可以产生2个值,它只被调用2次,使得它在第二个yield return
被击中后冻结。
如果您要求迭代器中的第三个元素,那么您的break
行将会被命中。
现在,为了说明最后一行被击中的场景,让我们以通常的方式使用枚举器(使用foreach
循环)。将您的Console.WriteLine
行替换为:
foreach (var dateTime in times)
Console.WriteLine(dateTime);
此代码将产生以下输出:
returning the value with index 0 11/17/2012 12:05:20 AM returning the value with index 1 11/18/2012 12:05:20 AM About to hit the `yield break`! Awesome!
正如您所看到的,foreach
消耗迭代器一直到最后,yield break
行被击中。您也可以通过在其上设置断点来确认这一点。
答案 1 :(得分:1)
假设你的意思是枚举器中的最后一行 从不运行......行
yield break;
不会执行,因为您只从具有两个元素的序列中获取两个元素(使用代码的初始版本)。枚举器后面的状态机永远不会足以执行该行。
当您尝试从序列中获取3个元素时,yield break;
确实会运行。
我认为没有理由(可能抛出异常)为什么不应该调用调用代码中的最后一行。
Console.WriteLine("Finished...");
如果这就是你的意思,是不是会抛出异常?如果是这样,那么例外的本质是什么?
重新审核上一次不正确的更新
最初编写的代码 执行
行Console.WriteLine("Finished...");
不执行
yield break;
出于前述原因。
随后添加到问题的行
Console.WriteLine("Third element:" + times.Skip(2).Take(1).Single().ToString());
不会成功,并且确实会抛出InvalidOperationException(“Sequence contains no elements”),并且会运行yield break;
行,因为您尝试从只有2的序列中获取3个元素。
更新2
如果您正在尝试了解迭代器块和Yield关键字的工作方式,我强烈建议Eric Lippert(Visual C#团队的首席开发人员)开头的系列博客文章
http://blogs.msdn.com/b/ericlippert/archive/2009/07/09/iterator-blocks-part-one.aspx
答案 2 :(得分:0)
当您尝试使用“第三个元素”(序列中的两个元素)时,将抛出异常,因为.Single()
调用无法返回。
顺便问一下,你知道.ElementAt(int)
方法吗?你为什么不用它?