Beware the siren of yield return

Onion Blog

Syndication

I was building an ASP.NET Ajax script control yesterday, and decided I'd try the new and improved ASP.NET Ajax Server Control wizard in Visual Studio 2008. The wizard creates a nice class that inherits from ScriptControl and provides an override of GetScriptDescriptors and GetScriptReferences that wire up the embedded JavaScript file used in the client. However, I noticed something curious about the default implementation of each of these methods: they used yield return as a way of returning a single instance of ScriptDescriptor and ScriptReference respectively as an enumerable collection (of 1). Here's what the override of GetScriptDescriptors() looks like:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()

{

ScriptControlDescriptor descriptor = new ScriptControlDescriptor("MyScriptControl.ClientControl1", this.ClientID);

yield return descriptor;

}

Now in the past, I have always implemented this method by dynamically allocating an array of one element since arrays already support IEnumerable:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()

{

ScriptControlDescriptor descriptor = new ScriptControlDescriptor("AjaxServerControl2.ClientControl1", this.ClientID);

return new ScriptDescriptor[] { descriptor };

}

When I first saw the use of yield return, I thought "Cool – I hadn't thought of using yield return that way!" It looked cleaner and shinier, without any messy brackets or braces, an elegant pair of keywords replacing a clunky array allocation. Then I began to have second thoughts… might it be less intuitive for someone reviewing the code? Hmm, debatable perhaps, but dynamically allocating an array to return a collection is a pretty common pattern in C#. Using yield return to concoct an enumerator implementation on the fly on top of one object, while pretty slick, may be less intuitive to understand.

But what occurred to me next, was the potential overhead of this technique. Since yield return will build an entirely new class to implement the IEnumerable interface automatically for you each time you use it, the amount of extra code generated by this technique could become rather significant if overused. To see just how much extra code we were looking at, I compiled two different versions of the code (actually a somewhat simplified version for simplicity) using each technique, and then opened each binary up in ildasm to compare the number of lines of IL generated. The array allocation function generated a total of 20 lines of IL, but the yield return function, if you included all of the IL instructions for the IEnumerable class generation as well was over 100! That's a 5x penalty in code generation to save 18 characters of typing J Just to confirm, I then built a small console application with 10 methods in it that return IEnumerable, and implemented all 10 methods with array allocation and then with yield return. The footprint of the release-compiled binary using the array allocation was 16k, and using the yield return method, 25k!

Moral of the story? Just because something looks shiny and new doesn't mean it's better… yield return is a powerful feature of C# that can be invaluable when you need an IEnumerable implementation over dynamic data, but for simple methods like GetScriptDescriptors() I'm going to stick with the tried and true method of array allocation.


Posted Feb 06 2008, 07:26 AM by fritz-onion
Filed under:

Comments

Scott Seely wrote re: Beware the siren of yield return
on 02-06-2008 7:10 AM
If you REALLY want to save the typing and have no 'keyboard penalty', you can just do this:

protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors()
{
return new ScriptDescriptor[] { new ScriptControlDescriptor("AjaxServerControl2.ClientControl1", this.ClientID) };
}

(Well, if typing was your only reason to do the yield return, we can remove one more bit of code without any extra obfuscation.)
Chris wrote re: Beware the siren of yield return
on 02-06-2008 8:43 AM
I have a feeling your story and subsequent advice is applicable to many new .net framework features. Sure, most new features are slick and sugary to look at -- but how often does a given scenario justify their use. System.Func<T, TResult>, Anonymous methods, LINQ, dynamic data controls, etc. Yes, there are new features that are useful and generally applicable to common scenarios; I appreciate the simple things like delegate inference and the generic Array.Foreach<T>([]T, delegate) but w/ so much hype around new features that allege to "drastically reduce the amount of code you need to write" the time it takes to learn everything and then separate the wheat from the chaff seems combined with the rate of change seems to nullify any purported time savings. Who is running the asylum? I think Microsoft is starting to make the framework too slick for their own good.
Joe Chung wrote re: Beware the siren of yield return
on 02-06-2008 9:33 AM
Stop spreading FUD, Chris. Write actual explanations of issues like Fritz did here instead.
Reo wrote re: Beware the siren of yield return
on 02-06-2008 10:40 AM
The 'FUD' is it is overhead growing at exponential rate. That is the reality check .NET developers need.
Steven wrote re: Beware the siren of yield return
on 02-07-2008 3:23 AM
Besides that all, I think you should avoid using 'yield return' from methods other than those that return an IEnumerator or IEnumerator<T> object. In other words: Do not use 'yield return' in methods returning IEnumerable. The reason for this is that the class, generator by the C# compiler, might implement IDisposable. And when returning an IEnumerable, your users (and maybe even yourself) may not be aware of this. When returning an IEnumerator you communicate very clearly that the user should dispose the returned object, since IEnumerator implements the IDisposable interface and 'IEnumerator GetEnumerator()' methods will almost always be used indirectly through the C# 'foreach' construct.
Andy wrote re: Beware the siren of yield return
on 02-08-2008 12:48 AM
But isn't the more interesting consideration about what happens to runtime performance, not LOC? (be it c# or IL).

I'm certainly more interested in the code being expressive, easy to understand. More likely to be error free and used as intended.

If you create a new array is that cheaper than creating a new Enumerator class or vice versa? Both in performance and memory terms.

Thoughts?
FAIL wrote re: Beware the siren of yield return
on 06-27-2008 2:29 AM

Epic Fail !

Eamon Nerbonne wrote re: Beware the siren of yield return
on 10-03-2008 11:34 AM

return Enumerable.Repeat(descriptor,1);

is yet another alternative with the same effect.

John Hackworth wrote re: Beware the siren of yield return
on 11-07-2008 8:22 AM

using VS 2005, I had some code that looked like this:

using (A_Stream)

{

    yield return xxx

}

A_Stream's destructor was not called, the file handle was not closed, and I had a tough time finding out why.

Add a Comment

(required)  
(optional)
(required)  
Remember Me?