Why do iterators behave differently on exception than LINQ enumerables?

徘徊边缘 提交于 2020-01-03 00:55:44

问题


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:

  1. The LINQ enumerator remains active. It skips an item but continues producing more.
  2. The iterator enumerator becomes finished. It does not produce any more items.

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}");
            }
        }
    }
}

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());

Output:

1
2
Exception: Oops!
4
5
Exception: Oops!
7
8
Exception: Oops!
10
Finished

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;
    }
}

Output:

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.


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.


回答1:


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.



来源:https://stackoverflow.com/questions/58257033/why-do-iterators-behave-differently-on-exception-than-linq-enumerables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!