Code in iterators gets executed only when calling the iterator's MoveNext() method, not upon calling the iterator function itself. (That call just sets up an IEnumerable object that contains the iterator code.)
Stuff that should happen immediately should therefore not go into an iterator method. A nice workaround is to pack the argument checking into a simple method, and call the generator from that simple method.
Guarded Iterator pattern:
public IEnumerable<T> Iterator (string argument)
{
// immediate argument checking
if (argument == null)
throw new ArgumentNullException ("argument");
// delayed iterator execution
return IteratorImplementation (argument);
}
private IEnumerable<T> IteratorImplementation (string argument)
{
yield return ...
}
Here's a simple example: Providing the lines of a stream as IEnumerable<string>
Naive implementation: Everything goes into the iterator method, argument checking is therefore delayed.
public static class StreamReaderExtensions
{
public static IEnumerable<string> Lines (this StreamReader reader)
{
if (reader == null)
throw new ArgumentNullException ("reader");
for (;;)
{
string line = reader.ReadLine ();
if (line == null)
yield break;
yield return line;
}
}
}
Fixed implementation: Argument checking in simple method up-front.
public static class StreamReaderExtensions
{
// this method is executed immediately when called
public static IEnumerable<string> Lines (this StreamReader reader)
{
if (reader == null)
throw new ArgumentNullException ("reader");
return LinesCore (reader);
}
// this iterator is executed partially whenever MoveNext is called on the generated enumerator
private static IEnumerable<string> LinesCore (StreamReader reader)
{
for (;;)
{
string line = reader.ReadLine ();
if (line == null)
yield break;
yield return line;
}
}
}
Here's how these methods work differently in a more complicated caller scenario:
StreamReader stream = null;
var numbers = from line in stream.Lines () // Fixed implementation throws ArgumentNullException here, when calling Lines()
select int.Parse (line);
foreach (var number in numbers) // Naive implementation throws ArgumentNullException here, when calling MoveNext()
Console.WriteLine(number * 2);
Thanks to Eric Lippert for pointing out the problem and providing the solution: http://blogs.msdn.com/ericlippert/archive/2008/09/08/high-maintenance.aspx (he makes another point there too, I simplified the example to concentrate on the exception problem.)
I believe this is a pattern, or at least an idiom, that should be considered whenever writing an iterator that does some argument checking. It deserves a name too, I like Guarded Iterator.