【发布时间】:2020-02-04 00:14:36
【问题描述】:
我正在研究iterator 方法的内部机制,我注意到迭代器获得的IEnumerator<T> 和LINQ 方法获得的IEnumerator<T> 之间的行为存在奇怪的差异。如果枚举过程中发生异常,那么:
- LINQ 枚举器保持活动状态。它会跳过一个项目,但会继续生产更多项目。
- 迭代器枚举器已完成。它不再生产任何物品。
示例。一个IEnumerator<int> 被顽固地枚举,直到它完成:
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 枚举器,它会在每 3 个项目上抛出一次:
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
完成了
现在让我们用一个迭代器尝试同样的方法,它会在每 3 个项目上抛出一个:
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
例外:糟糕!
完成了
我的问题是:这种不一致的原因是什么?哪种行为对实际应用更有用?
注意:此观察是在另一个与迭代器相关的问题中的 answer by Dennis1679 之后进行的。
更新:我做了更多观察。并非所有 LINQ 方法的行为都相同。例如,Take 方法在内部实现为 .NET Framework 上的 TakeIterator,因此它的行为类似于迭代器(异常立即完成)。但在 .NET Core 上,它的实现方式可能有所不同,因为在异常情况下它会继续运行。
【问题讨论】:
-
第一个代码示例应该很清楚了。由于您正在捕获每个异常,因此外部循环不会中断。
yield return的示例可能看起来有点奇怪,但编译器在幕后做了很多工作以使其像那样工作。 -
@Dennis_E 相同的方法
StubbornEnumeration用于枚举两个枚举器,即LINQ 和迭代器。结果是不同的。老实说,我没想到会存在这种差异。
标签: c# linq iterator enumeration