C# 为什么迭代器在异常情况下的行为与LINQ枚举不同?
我正在研究这些方法的内部机制,我注意到迭代器得到的结果与LINQ方法得到的C# 为什么迭代器在异常情况下的行为与LINQ枚举不同?,c#,linq,iterator,enumeration,C#,Linq,Iterator,Enumeration,我正在研究这些方法的内部机制,我注意到迭代器得到的结果与LINQ方法得到的IEnumerator在行为上有一个奇怪的差异。如果枚举期间发生异常,则: LINQ枚举器保持活动状态。它跳过一个项目,但继续产生更多 迭代器枚举器完成。它不生产更多的产品 例如。顽固地枚举IEnumerator,直到它完成: private static void StubbornEnumeration(IEnumerator<int> enumerator) { using (enumerator)
IEnumerator
在行为上有一个奇怪的差异。如果枚举期间发生异常,则:
IEnumerator
,直到它完成:
private static void StubbornEnumeration(IEnumerator<int> enumerator)
{
using (enumerator)
{
while (true)
{
try
{
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
Console.WriteLine("Finished");
return;
}
catch (Exception ex)
{
Console.WriteLine($"Exception: {ex.Message}");
}
}
}
}
输出:
12
例外:哎呀
4
5
例外:哎呀
7
8
例外:哎呀
10
完成 现在,让我们使用迭代器来尝试同样的方法,迭代器每3项抛出一次:
var linqEnumerable = Enumerable.Range(1, 10).Select(i =>
{
if (i % 3 == 0) throw new Exception("Oops!");
return i;
});
StubbornEnumeration(linqEnumerable.GetEnumerator());
StubbornEnumeration(MyIterator().GetEnumerator());
static IEnumerable<int> MyIterator()
{
for (int i = 1; i <= 10; i++)
{
if (i % 3 == 0) throw new Exception("Oops!");
yield return i;
}
}
枚举(MyIterator().GetEnumerator());
静态IEnumerable MyIterator()
{
对于(int i=1;i第二个示例中的yield
语法起作用。使用它时,编译器会生成一个状态机,该状态机在后台管理一个实枚举数。抛出异常会退出函数,从而终止状态机。第二个示例中的yield
语法使差异。当您使用它时,编译器会生成一个状态机来管理一个真正的枚举器。抛出异常会退出函数,从而终止状态机。有没有办法重写我的迭代器,使其行为类似于LINQ枚举器?是的,您可以构建自己的IEnumerable实现
和IEnumerator
(这正是LINQ在引擎盖下所做的),允许异常从MoveNext()中冒泡出来
而不将其内部状态设置为finished。您是否有任何解释,为什么迭代器的实现方式与日常开发人员手动编写IEnumerable
/IEnumerator
对的方式不一致?这两种方式都是基于上下文的。在LINQ的情况下,我们抛出了例外在lambda内部,每次迭代显式运行一次。在yield
的情况下,如果我们抛出一个异常,但以某种方式继续运行该函数而不是离开它,那么在这里,试图理解和解释该行为会有很多问题!我做了这个实验。我复制了我的迭代器粘贴到,获取生成的代码,修复了非法变量名以使其编译,并将其用作顽固枚举
的参数。它的行为类似于迭代器。是否有方法重写我的迭代器,使其行为类似于LINQ枚举?是的,您可以构建自己的IEnumerable
和IEnumerator
(这正是LINQ在幕后所做的)允许异常从MoveNext()冒泡出来
而不将其内部状态设置为finished。您是否有任何解释,为什么迭代器的实现方式与日常开发人员手动编写IEnumerable
/IEnumerator
对的方式不一致?这两种方式都是基于上下文的。在LINQ的情况下,我们抛出了例外在lambda内部,每次迭代显式运行一次。在yield
的情况下,如果我们抛出一个异常,但以某种方式继续运行该函数而不是离开它,那么在这里,试图理解和解释该行为会有很多问题!我做了这个实验。我复制了我的迭代器粘贴到,获取生成的代码,修复了非法变量名以使其进行编译,并将其用作顽固枚举
的参数。它的行为类似于迭代器。第一个代码示例应该很清楚。由于捕获了每个异常,外部循环不会中断。带有yield return
的示例可能看起来有点奇怪,但编译器确实如此Dennis_E相同的方法顽固枚举
用于枚举两个枚举数,LINQ枚举数和迭代器枚举数。结果是不同的。老实说,我没想到会存在这种差异。第一个代码示例应该很清楚。因为您捕获了所有异常,外部循环没有中断。yield return
的示例可能看起来有点奇怪,但编译器在幕后做了很多工作,使其能够像这样工作。@Dennis_E使用相同的方法顽固枚举
来枚举两个枚举器,LINQ枚举器和迭代器枚举器。结果不同。我没有体验到老实说,这种差异并不存在。