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

share save 171 16 Solving Issues With LINQ Foreach Variables

No related posts.

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

Tags: ,

Leave a Reply