Solving Issues With LINQ Foreach Variables

Can you guess what the output of the following program is?

using System;
using System.Collections.Generic;

namespace Lambda
{
    class Program
    {
        static void Main(string[] args)
        {
            var strings = new[] {"a", "b", "c"};
            var actions = CreateActions(strings);
            actions.ForEach(f => f());
        }

        private static List CreateActions(IEnumerable strings)
        {
            var actions = new List();

            foreach (var s in strings)
                actions.Add( () => Console.WriteLine(s) );

            return actions;
        }
    }
}

I expected to see all three strings (a, b and c), but instead I got this:
firstoutput Solving Issues With LINQ Foreach Variables

While analyzing the IL code with Reflector I learned a thing or two about lambdas in C#.
When a lambda is encountered by the compiler, it generates a class which has a field for each local variable used by the lambda. In this case, the generated class would look like this in C#:

private sealed class <>c_DisplayClass3
{
    public string s;
    public void 
b_0() { Console.WriteLine(this.s); } }

Of course, the above is not valid C# due to invalid characters in identifiers, but is pretty much what is produced in the IL.
So far so good – no real surprises here. However, look at this code which is my translation from IL to C# of the CreateActions method:

private static List CreateActions(IEnumerable strings)
{
    List actions = new List();
    using (IEnumerator enumerator = strings.GetEnumerator())
    {
        // Note 1: The lambda is created outside of the loop
        <>c_DisplayClass3 lambda = new <>c_DisplayClass3();

        string s;
        while (enumerator.MoveNext())
        {
            s = enumerator.Current;

            // Note 2: The instance field is reassigned each time
            lambda.s = s;
            actions.Add(lambda);
        }
    }
    return actions;
}

The reason for the strange output is clear now – the compiler did not create a lambda object in each iteration but rather reused the existing one each time. After the loop, all items in the actions list are actually the same instance and their s field is the last element of the collection. Apparently, C# compiler creates a lambda instance immediately before the local variable it uses. So, in order to force it to create a new lambda for each iteration, we need to introduce a new local variable in the loop:

private static List CreateActions(IEnumerable strings)
{
    var actions = new List();

    foreach (var s in strings)
    {
        // This variable will be used in the lambda
        var lambda_param = s;
        actions.Add( () => Console.WriteLine(lambda_param) );
    }

    return actions;
}

With this modification the program now produces this output:
secondoutput Solving Issues With LINQ Foreach Variables

GD Star Rating
loading...
GD Star Rating
loading...
  • Share/Bookmark

No related posts.

Related posts brought to you by Yet Another Related Posts Plugin.

Tags: ,

Leave a Reply