Geeks With Blogs

News

Series

Add to Technorati Favorites


An Archived Managed World This blog has moved to http://www.managed-world.com/blog

In this post I want to discuss with you the importance of realizing how lambdas work (and why you should care). Let's dive right in with some code.

Given the following code snippet, what would you expect the output to be (no cheating :P)?

            var actions = new List<Action>();

            for (int i = 0; i < 10; i++)
            {
                actions.Add(() => Console.WriteLine(i));
            }

            foreach (var action in actions)
            {
                action();
            }

Would you believe me if I told you this is the output you get?

10
10
10
10
10
10
10
10
10
10

At first glance, one might expect this output:

0
1
2
3
4
5
6
7
8
9

But all tens are output instead. Why does this happen? Let's crank open Reflector and find out why...

 

The first thing you'll notice is that the compiler has created a helper class to enable the closure we have. This helper class created by the compiler contains a local variable that we use to iterate over and a method that our delegate is contained within. This helper class is especially interesting because it is where the magic happens.

 

[CompilerGenerated]
private sealed class <>c__DisplayClass2
{
    // Fields
    public int i;

    // Methods
    public void <Main>b__0()
    {
        Console.WriteLine(this.i);
    }
}

Rather than diving into MSIL, let's look at some pseudo-code of what the compiler _actually_ executes with the original code above (based on the generated MSIL):

 

var actions = new List<Action>();

<>c__DisplayClass2 localFrame = new <>c__DisplayClass2();
for (localFrame.i = 0; localFrame.i < 10; localFrame.i++)
{
	actions.Add(localFrame.<Main>b__0);
}

foreach (var action in actions)
{
	action();
}

 

Perhaps now you can see where the problem is. The "problem" in our original code exists because of the scope that our closures are defined. The local index from our for loop is now stored in our helper class <>c__DisplayClass2. And the code that is executed by the action is contained within the compiler generated <Main>b__0() method now. So the Console.WriteLine() method now uses the local variable from <>c__DisplayClass2 when it is executed.

 

So while we are looping through building all our actions, we are also incrementing the property i in localFrame (an instance of <>c__DisplayClass2). Then at the end when we are actually executing the actions, the <Main>b__0() is called and uses the local i property (which by this time, has already been incremented to 10 from our loop). And that's why every action we execute prints "10" instead of the 0 through 9 like we expected.

 

So, why do you need to know how these work? Take the following code that outputs items from an array of strings:

 

            var actions = new List<Action>();
            string[] urls = 
            { 
                "http://www.url.com", 
                "http://www.someurl.com", 
                "http://www.someotherurl.com", 
                "http://www.yetanotherurl.com" 
            };

            for (int i = 0; i < urls.Length; i++)
            {
                actions.Add(() => Console.WriteLine(urls[i]));
            }

            foreach (var action in actions)
            {
                action();
            }

 

This code looks pretty innocuous. Our bounds are protected, and we just index into our array to output a string. But, is that what we're really doing? Remember how closures work from above. The actual thing that happens when I run this code is this:

 

Confusing

 

Interesting. Even though our index variable should only even be less than the length of our url array, an exception is thrown because the index variable is actually equal to the length of our url array (and hence outside of the bounds thanks to 0-based indices).

 

Well, that wasn't what we were probably expecting. But now that we are having this "problem", what is the easiest way to resolve it? Remember that the problem is happening because of the scope of the variable within our closure. So to fix this, we can essentially declare a temporary variable that is unique in scope to this specific iteration through our array:

 

            for (int i = 0; i < urls.Length; i++)
            {
                string localUrl = urls[i];
                actions.Add(() => Console.WriteLine(localUrl));
            }

 

And now the code is fixed.

 

Understanding how lambdas work is especially important when you start developing with a library that leverages lambdas heavily like LINQ does, or Parallel Extensions to the .NET Framework. And don't worry, even those people that know how lambdas work occasionally get bitten by this behavior.

 

Enjoy the coding, folks!

Posted on Friday, June 13, 2008 6:28 AM Development | Back to top


Comments on this post: Lambdas - Know Your Closures

# re: Lambdas - Know Your Closures
Requesting Gravatar...
Can you fix the image?

http://geekswithblogs.net/jolson/archive/2008/06/13/lambdas---know-your-closures.aspx
Left by Tony on Mar 15, 2009 9:55 PM

# re: Lambdas - Know Your Closures
Requesting Gravatar...
This was very insightful. I got bit by this recently and knew I had to change it but didn't understand why. Thanks!
Left by Dan Bailiff on Nov 29, 2011 7:05 AM

# re: Lambdas - Know Your Closures
Requesting Gravatar...
I would like to appreciate you for coming up with an extremely informative article about lambdas. snore aids However, after going through the blog, I realized that I am not the right person to try it out because it looks somewhat complicated for me.
Left by Sophie on Oct 27, 2015 4:29 PM

# re: Lambdas - Know Your Closures
Requesting Gravatar...
Snapchat Online: snapchat on line access
Tinder Online: tinder on line access
Left by David on Mar 18, 2016 1:33 AM

# re: Lambdas - Know Your Closures
Requesting Gravatar...
One of such intervention is the creation of Binary Option Robot auto trading robots. This development has indeed contributed hugely to the present growth and success of the industry as it has made it possible for everyone, including people with no basic knowledge on binary option or stock to participate and make a living on it. We can’t however talk about Binary Option Robot without mentioning Binary Robot 365.
Left by imo on Jul 30, 2017 12:52 AM

Your comment:
 (will show your gravatar)


Copyright © Jason Olson | Powered by: GeeksWithBlogs.net