As you use iterator functions (and yield) more and more, you’ll start to run into some limitations in the C# language. For instance, you can’t yield inside a try block that has a catch block. And the foreach loop doesn’t provide a very good way to catch exceptions when looping over an iterator function, either. Today’s article goes into detail to find solutions to these issues and make iterator functions usable in even the trickiest scenarios!

Let’s jump right in to the first scenario: you’re not allowed to yield in a try block that has a catch block. If you do, you’ll get this error:

Cannot yield a value in the body of a try block with a catch clause.

Here’s an example function that triggers that error:

private IEnumerable IteratorFunctionBroken()
{
	try
	{
		yield return "a";
		OtherFunction();
		yield return "b";
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
	}
}

The solution, clearly, is to move the yield statements out of the try block. In a simple function like the one above, that’s pretty easy:

private IEnumerable IteratorFunctionWorking()
{
	yield return "a";
	try
	{
		OtherFunction();
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
		yield break;
	}
	yield return "b";
}

Notice that an extra yield break had to be added in the catch block because an exception should have ended the execution of the try block.

As the try block grows, you’ll need to split the code into multiple try-catch blocks. Consider this slightly more complicated function:

private IEnumerable IteratorFunctionBroken()
{
	try
	{
		yield return "a";
		OtherFunction();
		yield return "b";
		YetOtherFunction();
		yield return "c";
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
	}
}

It needs to get split into two try-catch blocks:

private IEnumerable IteratorFunctionWorking()
{
	yield return "a";
	try
	{
		OtherFunction();
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
		yield break;
	}
	yield return "b";
	try
	{
		YetOtherFunction();
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
		yield break;
	}
	yield return "c";
}

Unfortunately, we now have duplicated code because both catch blocks are exactly the same. To solve this, you can either create a function with the catch block code in it, or use a goto to keep the exception handling within the same function. Here are both versions:

// 'catch' block replacement
private void HandleException(Exception ex)
{
	Debug.Log("exception: " + ex);
}
 
private IEnumerable IteratorFunctionWorking()
{
	yield return "a";
	try
	{
		OtherFunction();
	}
	catch (Exception ex)
	{
		// Call the 'catch' block code
		HandleException(ex);
		yield break;
	}
	yield return "b";
	try
	{
		YetOtherFunction();
	}
	catch (Exception ex)
	{
		// Call the 'catch' block code
		HandleException(ex);
		yield break;
	}
	yield return "c";
}
 
private IEnumerable IteratorFunctionWorking()
{
	yield return "a";
 
	// Holds the exception if one occurs
	Exception caughtException;
 
	try
	{
		OtherFunction();
	}
	catch (Exception ex)
	{
		// Assign to the exception at the function level
		caughtException = ex;
 
		// Go to the exception-handling code below
		goto handle_exception;
	}
	yield return "b";
	try
	{
		YetOtherFunction();
	}
	catch (Exception ex)
	{
		// Assign to the exception at the function level
		caughtException = ex;
 
		// Go to the exception-handling code below
		goto handle_exception;
	}
	yield return "c";
 
	// Break so we don't handle the exception when it hasn't
	// been caught
	yield break;
 
	// 'catch' block replacement
	handle_exception:
		Debug.Log("exception: " + ex);
}

The complexity of the workarounds grow with the complexity of the iterator function. For example, if we want to perform more code after the try-catch then we can’t yield break in the catch. Here’s an example:

private IEnumerable IteratorFunctionBroken()
{
	try
	{
		yield return "a";
		OtherFunction();
		yield return "b";
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
	}
 
	// This is something we'd like to do after the try-catch
	yield return "c";
}

To work around this we can once again employ goto to skip around in the function:

private IEnumerable IteratorFunctionWorking()
{
	yield return "a";
	try
	{
		OtherFunction();
	}
	catch (Exception ex)
	{
		Debug.Log("exception: " + ex);
 
		// Can't just 'yield break' here
		// That would skip the 'yield return c'
		// Skip the 'yield return b' using 'goto'
		goto after_try_catch;
	}
	yield return "b";
 
	// This label is for what came after the original function's try-catch
	after_try_catch:
		yield return "c";
}

With this in mind, we can solve a related problem. When we need to loop over an iterator function that returns IEnumerable or IEnumerable<T> it’s very nice to be able to use a foreach loop like so:

// Yield the values that IteratorFunction() yields
// This is an example of a nested iterator function
foreach (var cur in IteratorFunction())
{
	yield return cur;
}

What do we do if we want to catch exceptions that IteratorFunction throws? If we simply wrap the loop in a try-catch block then we’ll run into the above problem. Here’s how it’d look:

try
{
	foreach (var cur in IteratorFunction())
	{
		// This line will cause a compiler error
		// Not allowed to yield in a 'try' block that has a 'catch' block
		yield return cur;
	}
}
catch (Exception ex)
{
	Debug.Log("IteratorFunction() threw exception: " + ex);
}

The solution is to ditch the convenience of the foreach loop and replace it with a for loop. This allows us to isolate the calls to MoveNext() on the IEnumerator from the rest of the loop logic. Here’s how that solution looks:

IEnumerable enumerable = IteratorFunction();
IEnumerator enumerator = enumerable.GetEnumerator();
for (bool more = true; more; )
{
	// Catch exceptions only on executing/resuming the iterator function
	try
	{
		more = enumerator.MoveNext();
	}
	catch (Exception ex)
	{
		Debug.Log("IteratorFunction() threw exception: " + ex);
	}
 
	// Yielding and other loop logic is moved outside of the try-catch
	yield return enumerator.Current;
}

And with that we have an effective workaround for some of the most common C# language issues with iterator functions. It’s unfortunate that we need to add so much complexity to our code and consequently make it much more difficult to read, write, and maintain. If you know of a different or better way to work around these issues than what I’ve presented in the article, please let me know by posting your solution in the comments!