【问题标题】:How to properly throw an Exception inside yield return method in C#如何在 C# 中的 yield return 方法中正确抛出异常
【发布时间】:2016-07-08 16:35:45
【问题描述】:

请参阅下面的编辑以重现我在此问题中描述的行为。

以下程序永远不会结束,因为 C# 中的 yield return 构造在抛出异常时会无限期地调用 GetStrings() 方法。

class Program
{
    static void Main(string[] args)
    {
        // I expect the Exception to be thrown here, but it's not
        foreach (var str in GetStrings())
        {
            Console.WriteLine(str);
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        // REPEATEDLY throws this exception
        throw new Exception();
        yield break;
    }
}

对于这个简单的例子,我显然可以改用return Enumerable.Empty&lt;string&gt;();,问题就消失了。然而,在一个更有趣的例子中,我希望异常被抛出一次,然后停止调用该方法并在“消耗”IEnumerable 的方法中抛出异常。

有没有办法产生这种行为?

编辑:好的,问题与我最初想的不同。上面的程序没有结束,foreach 循环的行为就像一个无限循环。下面的程序确实结束了,并且控制台上显示了异常。

class Program
{
    static void Main(string[] args)
    {
        try
        {
            foreach (var str in GetStrings())
            {
                Console.WriteLine(str);
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        throw new Exception();
        yield break;
    }
}

为什么try ... catch 块在这种情况下会有所不同?这对我来说似乎很奇怪。感谢@AndrewKilburn 已经为我指出了这一点。

编辑#2: 在命令提示符下,程序在两种情况下都执行相同的操作。在 Visual Studio Enterprise 2015 更新 2 中,无论我在 Debug 还是 Release 中编译,上面的行为都是我所看到的。使用try ... catch,程序以异常结束,没有它,Visual Studio 永远不会关闭程序。

编辑 #3:已修复 对我来说,@MartinBrown 的回答解决了这个问题。当我在 Debug > Options > Debugging > General > “Unwind the call stack on unhandled exceptions”下取消选中 Visual Studio 的选项时,这个问题就消失了。当我再次选中该框时,问题又回来了。

【问题讨论】:

  • 我试过了,它只抛出一次。在用try catch 包装foreach 的情况下,异常也只被捕获一次并且程序退出。我理解正确吗?
  • 嗯,我没有看到这种行为。让我再次确认一下。我看到的行为是抛出异常,它在幕后以某种方式被捕获,foreach 循环的行为就像一个无限循环。
  • 我的版本和你一模一样,操作系统也一模一样。在任何抖动和任何框架目标以及任何可用的调试引擎上都会像一吨砖一样崩溃。你需要修好你的机器。
  • 根据您的最后一条评论,您看到的行为似乎是由 Visual Studio 的调试器引起的,该调试器吞下了未处理的异常(不带 try/catch 的版本)?我是否正确解释(编辑#2)?我什至不确定 VS 中是否有这样的设置可以做到这一点,但也许它会有助于发布你的 VS 调试设置的屏幕截图。
  • n/m,我看到你已经接受了我所想的答案。

标签: c# exception yield-return


【解决方案1】:

这里看到的行为不是代码中的错误;相反,它是 Visual Studio 调试器的副作用。这可以通过在 Visual Studio 中关闭堆栈展开来解决。尝试进入 Visual Studio 选项 Debugging/General 并取消选中“Unwind the call stack on unhandled exceptions”。然后再次运行代码。

当您的代码遇到完全未处理的异常时,Visual Studio 会将调用堆栈展开到代码中导致异常的行之前。这样做是为了让您可以编辑代码并继续使用编辑后的代码执行。

这里看到的问题看起来像一个无限循环,因为当您在调试器中重新开始执行时,下一行要运行的是刚刚导致异常的行。在调试器之外,调用堆栈将在未处理的异常上完全展开,因此不会导致您在调试器中遇到的相同循环。

这个堆栈展开功能可以在设置中关闭,默认开启。但是,将其关闭将阻止您编辑代码并继续,而无需先自己展开堆栈。然而,这很容易通过调用堆栈窗口或从异常助手中选择“启用编辑”来完成。

【讨论】:

  • 这解决了这个问题。任何解释为什么?
  • 遗憾的是,我对它的实际作用的了解有点有限。如果你弄明白了,请告诉我。
  • @JohnCarpenter Here 描述了功能。 (可以在cmets中找到描述)
  • @MatteoUmili 在阅读了一些关于该主题的内容后,我已经填写了一些答案。
  • @Mike 看来,是的,它已经在 VS2017 中消失了。 :( 请参阅 Hans Passant 对此问题的评论:stackoverflow.com/questions/43126140/…
【解决方案2】:

以下程序永远不会结束

这是错误的。该计划将很快结束。

因为 C# 中的 yield return 构造在抛出异常时会无限期地调用 GetStrings() 方法。

这是错误的。它根本不这样做。

我希望异常抛出一次,然后停止调用该方法并在“消耗”IEnumerable 的方法中抛出异常。

这正是发生的

有没有办法产生这种行为?

使用您已经提供的代码。

【讨论】:

  • 事实证明并非如此。显然,Visual Studio 中的行为有所不同。或者至少看起来不同。 :)
  • 非常没有帮助
【解决方案3】:
  class Program {
        static void Main(string[] args) {
            try {
                foreach (var item in GetStrings()) {
                    Console.WriteLine();
                }
            }
            catch (Exception ex) {

        }
    }
    private static IEnumerable<string> GetStrings() {
        // REPEATEDLY throws this exception
        throw new Exception();
        yield break;
    }
}

将它放入 try catch 会导致它爆发并做任何你想做的事情

【讨论】:

  • 不幸的是,这不起作用,因为在 Main 方法中根本没有抛出异常。
  • 这很有趣...放置 try catch 会使程序的行为有所不同。如果没有try ... catch,程序会为你结束吗?
  • @JohnCarpenter Mines 同样,它并没有结束。异常不断被抛出,但它没有绕过 foreach
【解决方案4】:
class Program
{
    public static int EnumerableCount;

    static void Main(string[] args)
    {
        EnumerableCount = 0;
        try
        {
            foreach (var str in GetStrings())
            {
                Console.WriteLine(str);
                Console.Read();
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
            Console.Read();
        }
    }

    private static IEnumerable<string> GetStrings()
    {
        EnumerableCount++;
        var errorMessage = string.Format("EnumerableCount: {0}", EnumerableCount);
        throw new Exception(errorMessage);
        yield break;
    }
}

有以下输出:

System.Exception: EnumerableCount: 1
  at {insert stack trace here}

执行流程进入 GetStrings() 方法,在第一次迭代中,异常被抛出并在 Main() 方法中捕获。回车后,程序退出。

删除 Main() 方法中的 try catch 会导致异常未处理。那么输出是:

Unhandled Exception: System.Exception: EnumerableCount: 1
  at {insert stack trace here}

然后程序崩溃了。

【讨论】:

    猜你喜欢
    • 2019-05-04
    • 2015-04-29
    • 2016-04-03
    • 2011-07-28
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-22
    相关资源
    最近更新 更多