Obscure IEnumerator facts

Daniel Fortunov wrote about an obscure use of duck typing in the C# spec for enumerators:

Although it is common to implement [IEnumerable and IEnumerator] when creating an enumerable class, if you were to drop the interfaces but leave the implementation, your class would still be enumerable by foreach. Voila! Duck-typing!

You can take advantage of this fact for a couple of performance tricks, as demonstrated by many of the standard collection classes in the base class library:

  • Declare your IEnumerator<T> implementation as a struct, not a class. This saves you a heap allocation when MoveNext is called.
  • Define a Enumerator<T> GetEnumerator() method on your collection class
  • Note that you're returning your own struct, not IEnumerable<T>; this avoids a boxing operation. You'll still need to explicitly implement IEnumerator<T> GetEnumerator(), for people who only have an IEnumerable<T> reference to your collection. These performance tricks don't apply when you're making calls through this interface.

When somebody uses foreach over your collection, the compiler sees a series of MoveNext calls and accesses to the Current property, and it emits code to call these efficiently on your struct.

What's more, the code in your struct's methods is a candidate for inlining by the JIT compiler. The segment of the MoveNext method of System.Collections.Generic.List<T>+Enumerator that can throw an exception is split into its own method, apparently for this reason.

I don't claim any kind of definite performance benefits from using these techniques, but it does look like the language designers put some thought into making it possible to use foreach without incurring any overhead compared to some less elegant method.

2 Comments

old one but worth noting that:
1. this only works if the compiler knows the exact type of the collection (if it's via an IEnumerable variable for example boxing still occurs.

2. it can expose your program to a very subtle bug involving copies of the enumerator. This is very rare (a good thing since List does it!) but is worth knowing about:

http://www.eggheadcafe.com/software/aspnet/31702392/c-compiler-challenge--s.aspx

Shuggy, this is truly nasty - it took me a little while to spot the problem. I take it the bug is due to the fact that what look like fields on an anonymous type are in fact properties, and a fresh clone of the List<string>.Enumerator is created each time it's accessed.

As Jon Skeet points out on the site, mutable structs are evil.

Leave a comment

Close