如何在 C# 中的收益返回方法中正确抛出异常

本文关键字:方法 抛出异常 返回 收益 | 更新日期: 2023-09-27 17:55:38

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

以下程序永远不会结束,因为 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<string>();代替,问题就消失了。 但是,在一个更有趣的示例中,我希望将异常抛出一次,然后停止调用该方法并在"消耗"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 Update 2中,无论我在调试还是发布中编译,上述行为都是我所看到的。 随着try... catch,程序以异常结束,没有它,Visual Studio永远不会关闭程序。

编辑#3:固定对我来说,这个问题通过@MartinBrown的答案解决了。 当我取消选中"调试>选项>"下的"调试选项">常规">"在未经处理的异常上展开调用堆栈"时,此问题消失了。 当我再次选中该框时,问题又回来了。

如何在 C# 中的收益返回方法中正确抛出异常

这里看到的行为不是代码中的错误;而是Visual Studio调试器的副作用。这可以通过在Visual Studio中关闭堆栈展开来解决。尝试进入Visual Studio选项调试/常规并取消选中"在未处理的异常时展开调用堆栈"。然后再次运行代码。

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

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

此堆栈展开功能可以在设置中关闭,默认情况下处于启用状态。但是,将其关闭将阻止您编辑代码并继续,而无需先自己展开堆栈。但是,无论是从调用堆栈窗口还是简单地从异常助手中选择"启用编辑",这都很容易做到。

以下程序永远不会结束

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

因为 C# 中的收益返回构造在引发异常时无限期地调用 GetStrings() 方法。

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

我希望异常被抛出

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

这正是确实发生的事情。

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

使用您已经提供的代码。

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

把它放在一个尝试捕捉中会导致它爆发并做任何你想做的事情

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}

程序崩溃。