为什么迭代器在异常方面的行为与LINQ可枚举不同? [英] Why do iterators behave differently on exception than LINQ enumerables?
问题描述
我正在研究 iterator 方法的内部机制,并且我注意到通过迭代器获得的IEnumerator<T>
和通过LINQ方法获得的IEnumerator<T>
.如果枚举期间发生异常,则:
I am studying the internal mechanics of the iterator methods, and I noticed a strange difference in behavior between the IEnumerator<T>
obtained by an iterator and the IEnumerator<T>
obtained by a LINQ method. If an exception happens during the enumeration, then:
- LINQ枚举器保持活动状态.它会跳过一个项目,但会继续生产更多产品.
- 迭代器枚举器完成.它不再生产任何物品.
示例. IEnumerator<int>
会被顽固地枚举,直到完成:
Example. An IEnumerator<int>
is enumerated stubbornly until it completes:
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}");
}
}
}
}
让我们尝试枚举在第三个项目上抛出的LINQ枚举器:
Let's try enumerating a LINQ enumerator that throws on every 3rd item:
var linqEnumerable = Enumerable.Range(1, 10).Select(i =>
{
if (i % 3 == 0) throw new Exception("Oops!");
return i;
});
StubbornEnumeration(linqEnumerable.GetEnumerator());
输出:
1
2
例外:糟糕!
4
5
例外:糟糕!
7
8
例外:糟糕!
10
完成
1
2
Exception: Oops!
4
5
Exception: Oops!
7
8
Exception: Oops!
10
Finished
现在让我们使用在每个第3个项目上抛出的迭代器进行相同的尝试:
Now let's try the same with an iterator that throws on every 3rd item:
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;
}
}
输出:
1
2
例外:糟糕!
完成
1
2
Exception: Oops!
Finished
我的问题是:这种不一致的原因是什么?哪种行为对实际应用更有用?
My question is: what is the reason for this inconsistency? And which behavior is more useful for practical applications?
Note: This observation was made following an answer by Dennis1679 in another iterator-related question.
更新:我还做了一些观察.并非所有LINQ方法的行为都相同.例如,Take
方法在内部实现为 .c Framework上的TakeIterator
,因此其行为类似于迭代器(异常立即完成).但是在.NET Core上,它的实现方式可能有所不同,因为在例外情况下,它会继续运行.
Update: I made some more observations. Not all LINQ methods behave the same. For example the Take
method is implemented internally as a TakeIterator
on .NET Framework, so it behaves like an iterator (on exception completes immediately). But on .NET Core it's probably implemented differently because on exception it keeps going.
推荐答案
第二个示例中的yield
语法有所不同.使用它时,编译器会生成一个状态机,该状态机在后台管理真正的枚举器.引发异常会退出该函数,因此会终止状态机.
The yield
syntax in the second example makes the difference. When you use it, the compiler generates a state machine that manages a real enumerator under the hood. Throwing the exception exits the function and therefore terminates the state machine.
这篇关于为什么迭代器在异常方面的行为与LINQ可枚举不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!