An iterator method is not executed until the return value is enumerated. It's therefore advantageous to assert preconditions outside of the iterator.
public static IEnumerable<int> Count(int start, int count)
{
// The exception will throw when the method is called, not when the result is iterated
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
return CountCore(start, count);
}
private static IEnumerable<int> CountCore(int start, int count)
{
// If the exception was thrown here it would be raised during the first MoveNext()
// call on the IEnumerator, potentially at a point in the code far away from where
// an incorrect value was passed.
for (int i = 0; i < count; i++)
{
yield return start + i;
}
}
Calling Side Code (Usage):
// Get the count
var count = Count(1,10);
// Iterate the results
foreach(var x in count)
{
Console.WriteLine(x);
}
Output:
1
2
3
4
5
6
7
8
9
10
When a method uses yield
to generate an enumerable the compiler creates a state machine that when iterated over will run code up to a yield
. It then returns the yielded item, and saves its state.
This means you won't find out about invalid arguments (passing null
etc.) when you first call the method (because that creates the state machine), only when you try and access the first element (because only then does the code within the method get ran by the state machine). By wrapping it in a normal method that first checks arguments you can check them when the method is called. This is an example of failing fast.
When using C# 7+, the CountCore
function can be conveniently hidden into the Count
function as a local function. See example here.