Yesterday, I spent an embarrassing amount of time searching for a bug. I’m sure this is well-documented somewhere on MSDN. It even generates a compiler warning in some cases. Still, it’s not the behavior one would expect from C#.
When creating lambdas (including LINQ expressions) inside a for each loop, don’t use the iterator variable in your lambda. Let me explain with some code:
foreach (Type controllerType in controllerTypes)
{
kernel.Bind(controllerType).ToSelf().InRequestScope();
kernel.Bind<IController>().ToMethod(ctx => ctx.kernel.Get(controllerType)).Named(GetName(controllerType));
}
Why am I using a method to get the controller? It just so happens that my AccountController is also a domain event handler for my AccountNameAlreadyUsed event. This goes back to Ayende’s tip in my domain events post: To get a message back to the UI, fire a new event and have the UI listen for it. In this case, I need the UI to complain when the account name they selected is already being used.
In case your mind has wandered to the dark side, throwing exceptions is not an acceptable way of passing messages in an application.
Now, why the odd mappings? Let’s say I bind IController and IHandle<AccountNameAlreadyUsed> to AccountController in the request scope. It doesn’t quite work like you would first expect. You will have one instance of AccountController returned for IController and a separate instance for IHandle<AccountNameAlreadyUsed>.
Instead, I’m saying that for each request for an IController, go get an AccountController, essentially delegating the request to the ToSelf() binding. I have a similar ToMethod() lambda binding for IHandle<AccountNameAlreadyUsed>. Since both interface bindings are fulfilled by the ToSelf binding, only one instance of AccountController is created for the request, instead of two.
So, this explains why I need the lambda in the first place. Why didn’t the code above work?
As it turns out, there was some funny business going on underneath the covers between the foreach iterator and the lambda. Here’s the symptom: No matter which “instance” of the lambda was being referenced, inside the lambda, the iterator variable (controllerType) was always the first value that was iterated. No matter which controller I requested, I always got an instance of AccountController, since it happens to be first alphabetically.
Once you realize what’s going on, the fix is simple. Create another variable and use it inside the lambda instead. So, instead of the code above, we get this:
foreach (Type controllerType in controllerTypes)
{
var controllerType2 = controllerType;
kernel.Bind(controllerType).ToSelf().InRequestScope();
kernel.Bind<IController>().ToMethod(ctx => ctx.kernel.Get(controllerType2)).Named(GetName(controllerType));
}
If, instead of a lambda, we had a LINQ expression, the compiler would generate a warning about this issue – at least in VB.NET. If I hadn’t already seen that warning from LINQ expressions, I would probably still be bug hunting.
Jason




#1 by Michael Stum on February 17th, 2010
Eric Lippert (who else?) has two important posts about this. Basically you are only capturing the reference to the loop variable as it’s delay executed:
Closing over the loop variable considered harmful:
http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
Closing over the loop variable, part two:
http://blogs.msdn.com/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx
As a C# dev, I really recommend subscribing to his blog. He is one of the Language Designers and his postings are really insightful, as he has the gift of being able to explain stuff clearly.
HTH!
#2 by Simon on February 24th, 2010
I know this is off topic here but I couldn’t find any other way of geting in touch with you about the N* Stack project.
I am trying to follow along as a way to learn more about MVC and have got all the way to the end of part 10 and the project does not run. I tried the download you provided but even when I fix hard coded paths the website does not work. Do you have a finished solution I could download?
Do you have any plans to finish the series off?
#3 by Jason on February 25th, 2010
@Simon – It’s on hold for while. You can always find me on twitter: @jasondentler
#4 by Simon on February 27th, 2010
@Jason – thanks for getting back to me.
Sorry I haven’t got into twitter yet (I haven’t made the jump from msn) Do you have a download that runs a basic site or high level details of what needs doing so I can finish things off on my own.
I’ve managed to get a homepage to run by adding it back into the .web project in views – but you said to delete the views in the 1st post so I guess they don’t belong there now?
#5 by Jason on February 28th, 2010
We deleted them because the views that come with the project template don’t match up with the controllers I have planned.
I always keep my views in the standard WebProject \ Views \ ControllerName \ View.aspx location.
All of the NHibernate stuff is basically done. You shouldn’t have much trouble finishing up this project on your own.