Oooh, pretty code
Monday, February 02, 2009 2:46:13 PM (Pacific Standard Time, UTC-08:00) ( CMPT 376 | Code Oddities | Tips )
I recently used the ‘yield’ keyword in a C# method and ran into a strange issue when trying to verify arguments. I always try to verify the arguments provided to my methods so that I can throw ArgumentException or ArgumentNullException as necessary. However, when you use the ‘yield’ keyword to create an enumerator in C#, you can run into issues
ArgumentException
ArgumentNullException
The ‘yield’ keyword lets you easily create a method which returns an enumerator (which can then be used in a foreach loop). For example, if I wanted to make a method that took a DirectoryInfo (representing a directory on disk) and returned file names of all the .txt files in it, I would write this:
foreach
DirectoryInfo
public IEnumerable<string> GetTextFiles(DirectoryInfo dir) { if (dir == null) { throw new ArgumentNullException("dir"); } foreach (FileInfo file in dir.GetFiles("*.txt")) { yield return file.Name; } }
However, the C# compiler will do something tricky with this method, because of the 'yield' keyword. It will create a custom implementation of IEnumerable<string> class that uses "magic" (I'm not going to go into detail as to how it works :P) to jump in and out of the code you wrote. Then, it compiles our method to the following (as decompiled with RedGate's .Net Reflector, a free tool every .Net developer must have :D):
IEnumerable<string>
public IEnumerable<string> GetTextFiles(DirectoryInfo dir) { <GetTextFiles>d__0 d__ = new <GetTextFiles<d__0(-2); d__.<>4__this = this; d__.<>3__dir = dir; return d__; }
Wait a sec, where'd our code go? It looks like it’s gone into this <GetTextFiles>d__0 class. If you think that name sounds compiler-generated, you’re right! It’s the custom implementation of IEnumerable<string> that was created by the compiler. Cool, eh? However, there’s a side effect here. Where did our argument checking code go? Unless there’s something in the <GetTextFiles>d__0 constructor, the user could pass null in for the dir parameter and no exception would be thrown! So, let’s take a look at the <GetTextFiles>d__0 constructor (using Reflector again):
<GetTextFiles>d__0
null
dir
[DebuggerHidden] public <GetTextFiles>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; }
Nope, nothing there. If you keep checking around the <GetTextFiles>d__0 class, you’ll find it in the MoveNext method on that class. That means that instead of checking the argument when you call the method, the argument won’t be checked until the first time MoveNext is called (i.e. the first iteration of a foreach loop). If, instead, we wrap the actual “yield” code in it’s own method, and call it from our original method (after argument checks), we’ll get the desired result:
MoveNext
public IEnumerable<string> GetTextFiles(DirectoryInfo dir) { if (dir == null) { throw new ArgumentNullException("dir"); } return InternalGetTextFiles(dir); } private IEnumerable<string> InternalGetTextFiles(DirectoryInfor dir) { foreach (FileInfo file in dir.GetFiles("*.txt")) { yield return file.Name; } }
Now, the "magic" is occurring in InternalGetTextFiles and GetTextFiles is compiled without modification and our argument checking works just fine.
InternalGetTextFiles
GetTextFiles
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
RSS
Sign In