简体   繁体   中英

How to properly throw an Exception inside yield return method in C#

See edits below for reproducing the behavior that I describe in this problem.

The following program will never end, because the yield return construct in C# calls the GetStrings() method indefinitely when an exception is thrown.

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

For this trivial example, I could obviously use return Enumerable.Empty<string>(); instead, and the problem goes away. However in a more interesting example, I'd expect the exception to be thrown once, then have the method stop being called and throw the exception in the method that's "consuming" the IEnumerable .

Is there a way to produce this behavior?

EDIT: ok, the problem is different than I first thought. The program above does NOT end, and the foreach loop behaves like an infinite loop. The program below DOES end, and the exception is displayed on the console.

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

Why does the try ... catch block make a difference in this case? This seems very strange to me. Thanks to @AndrewKilburn for his answer already for pointing me to this.

EDIT #2: From a Command Prompt, the program executes the same in both cases. In Visual Studio Enterprise 2015, Update 2, whether I compile in Debug or Release, the behavior above is what I am seeing. With the try ... catch , the program ends with an exception, and without it Visual Studio never closes the program.

EDIT #3: Fixed For me, the issue was resolved by the answer by @MartinBrown. When I uncheck Visual Studio's option under Debug > Options > Debugging > General > "Unwind the call stack on unhandled exceptions" this problem goes away. When I check the box again, then the problem comes back.

The behaviour being seen here is not a fault in the code; rather it is a side effect of the Visual Studio debugger. This can be resolved by turning off stack unwinding in Visual Studio. Try going into Visual Studio options Debugging/General and unchecking "Unwind the call stack on unhandled exceptions". Then run the code again.

What happens is that when your code hits a completely unhandled exception Visual Studio is unwinding the call stack to just before the line in your code that caused the exception. It does this so that you can edit the code and continue execution with the edited code.

The issue seen here looks like an infinite loop because when you re-start execution in the debugger the next line to run is the one that just caused an exception. Outside the debugger the call stack would be completely unwound on an unhandled exception and thus would not cause the same loop that you get in the debugger.

This stack unwinding feature can be turned off in the settings, it is enabled by default. Turning it off however will stop you being able to edit code and continue without first unwinding the stack yourself. This however is quite easy to do either from the call stack window or by simply selecting 'Enable Editing' from the Exception Assistant.

The following program will never end

That's false. The program is going to end quite quickly.

because the yield return construct in C# calls the GetStrings() method indefinitely when an exception is thrown.

This is false. It doesn't do this at all.

I'd expect the exception to be thrown once, then have the method stop being called and throw the exception in the method that's "consuming" the IEnumerable .

That's exactly what does happen.

Is there a way to produce this behavior?

Use the code you already provided.

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

Putting it in a try catch causes it to break out and do whatever you want

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

has the following output:

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

The execution flow goes into the GetStrings() method the for the first iteration, the exception is thrown and caught in the Main() method. After hitting enter, the program exits.

Removing the try catch in the Main() method causes the exception to go unhandled. The output is then:

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

and the program crashes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM