为什么迭代器在异常方面的行为与LINQ可枚举不同? [英] Why do iterators behave differently on exception than LINQ enumerables?

查看:52
本文介绍了为什么迭代器在异常方面的行为与LINQ可枚举不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在研究 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:

  1. LINQ枚举器保持活动状态.它会跳过一个项目,但会继续生产更多产品.
  2. 迭代器枚举器完成.它不再生产任何物品.

示例. 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆