Deferred execution turns a perfectly ordinary guard clause into a bug.
C#
static IEnumerable<int> FirstN(int[] source, int count)
{
if (source == null)
{
throw new ArgumentNullException(nameof(source));
}
for (int i = 0; i < count && i < source.Length; i++)
{
yield return source[i];
}
}
var seq = FirstN(null, 3); // no exception here
Console.WriteLine("still fine");
foreach (var n in seq) { } // ArgumentNullException — thrown *here*
The guard is inside the iterator, so it runs when the first value is requested — which may be a
different method, a different layer, or a different thread, long after the mistake was made. The
stack trace points at the innocent caller who enumerated, not the one who passed null.
The fix: two methods
Split it. An ordinary method validates and returns; a private iterator produces.
Guard outside, yield inside
One method — checks late
C#
static IEnumerable<int> FirstN(
int[] source, int count)
{
if (source == null)
throw new ArgumentNullException(
nameof(source));
for (int i = 0;
i < count && i < source.Length;
i++)
{
yield return source[i];
}
}Two methods — checks now
C#
static IEnumerable<int> FirstN(
int[] source, int count)
{
if (source == null)
throw new ArgumentNullException(
nameof(source));
return Iterate(source, count);
}
static IEnumerable<int> Iterate(
int[] source, int count)
{
for (int i = 0;
i < count && i < source.Length;
i++)
{
yield return source[i];
}
}The outer method contains no yield, so it is not an iterator — it runs immediately. This is exactly how LINQ's own operators are written, and why Where(null) throws at the call and not at the loop.
Try it: Task 2
Open the editorTry it: Task 3
Open the editor