Beware of the IEnumerables


Some time ago I wrote about the difference between List and Collection, and how you should use them. Usually you want to keep the details away from the users of your class, and an even tighter way to encapsulate a list of something is to expose IEnumerable<T>. Note that both IList<T> and ICollection<T> inherits this interface, so IEnumerable<T> exposes even less details to the user. Another benefit is now you can use yield behind the scenes to create your “list” on the fly.

But this also its pitfall. What do you think will be the output of this code?

   10   static void Main(string[] args)

   11   {

   12       var ola = new Pupil(){Name="Ola", IsMate = false};

   13       var trond = new Pupil() {Name = "Trond", IsMate = true};

   14       var pupils = new List<Pupil>(){ola,trond};

   15 

   16       IEnumerable<string> names = ClassMateNames(pupils);

   17 

   18       foreach (string name in names)

   19       {

   20           Console.WriteLine(name);

   21       }

   22   }

   23 

   24   private static IEnumerable<string> ClassMateNames(IEnumerable<Pupil> pupils)

   25   {

   26       foreach (Pupil pupil in pupils)

   27       {

   28           if (pupil.IsMate)

   29               yield return pupil.Name;

   30       }

   31   }

 

Yes, you’re right, the output is my only friend “Trond”:

image

But when are we really finding out who my friends are? The most natural thing is that I find them when I call ClassMateNames, as it is called first. That’s what I’d think anyway. But what if got in a fight with “Trond” between when I sort out my friends and list them to the console?

   35   private static void FightWith(Pupil pupil)

   36   {

   37       pupil.IsMate = false;

   38   }

   16   IEnumerable<string> names = ClassMateNames(pupils);

   17 

   18   FightWith(trond);

   19 

   20   foreach (string name in names)

   21   {

   22       Console.WriteLine(name);

   23   }

This gives me these friends:

image

How about that! The information that I no longer is friends with “Trond” has changed the list I thought I retrieved with the call to ClassMateNames. What is happening is called deferred execution, which delays the actual execution of the internals in the ClassMateNames to when I iterate it. This is exactly how LINQ to [insert TLA here] operate as well, so nothing is actually fetched until you need it.

In my case this was an advantage, because at the time I was listing my friends, I didn’t actually have any!

And the problem is?

The problem starts when you before and/or after the call to ClassMateNames set up and tear down a session, connection, or anything that the inside of the iterator rely on. Any fetching of IEnumerables inside using-blocks should also be considered smells in this regard. If you should clear the list of pupils right after you have sent it to ClassMateNames (you don’t need it anymore right?), you have introduced a very subtle bug as the list is empty at execution.

The advice are thus: IEnumerables and yield is definitively great tools, but be aware of the inherent difficulties and quirks of deferred execution and use them with caution both as producer and consumer!

  1. No comments yet.
(will not be published)

  1. No trackbacks yet.