Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Traditional loop versus LINQ - different outputs

Tags:

c#

.net

linq

In the below piece of C# code, why does the first set of prints produce an output of

C
C
C

but the LINQ equivalent of that produces an output of

A
B
C

I understand the first set of outputs - it takes last value when it exits the loop, but seems to me that there should be consistency between the traditional loop and LINQ equivalents? - Either it should print CCC or ABC in both cases?

public static void Main(string[] str)
    {
        List<string> names = new List<string>() {"A", "B", "C"};

        List<Action> actions = new List<Action>();
        foreach(var name in names)
        {
            actions.Add(() => Console.WriteLine(name));
        }

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

        List<Action> actionList = names.Select<string, Action>(s => () => Console.WriteLine(s)).ToList();

        foreach(var action in actionList)
        {
            action.Invoke();
        }
    }
like image 203
govin Avatar asked Jun 01 '13 02:06

govin


2 Answers

It's because you're closing over a loop variable. I simply can't explain it better than Lippert. (Can anybody?) If you're still confused, take some extra time to ponder it and read the comments on his blog -- they should be enlightening.

This is a very common mistake when using Linq. Just about everyone has made it. In the C# 5.0 (used in Visual Studio 2012) compiler, this behavior has changed, but you should still avoid doing it if you can help it. You could rewrite the first loop as:

foreach(var name in names)
    {
        var currentName = name;
        actions.Add(() => Console.WriteLine(currentName));
    }

and the problem will go away.

like image 51
Dave Markle Avatar answered Oct 07 '22 03:10

Dave Markle


I wanted to add to Dave Markle's explanation. He's absolutely right when he says this is because of "closing over a loop variable". To understand why this happens you have to go back to how closures work with delegates. Take a look at the following simple case without loops:

class Program
{

    delegate void TestDelegate();

    static void Main(string[] args)
    {
        List<string> names = new List<string>() { "A", "B", "C" };
        var name = names[0];

        TestDelegate test = () => { Console.WriteLine(name); };
        name = names[1];

        test();

        Console.ReadLine();
    }
}

What actually prints out here is "B" not "A". The reason is because the reference name points to has changed and when you called test().

When C# compiles your code it's magic sauce essentially changes your lambda expressions into delegates such as the one in the code above, and underneath the hood your name variable is just a reference, when name changes the call to test() will return a different result. As you've looped through, the last item in the list was what name was set to last and therefore when the action is finally invoked the name is only pointing to the last item in the list which is what gets printed. I hope my explanation isn't too verbose.

Just imagine that when if we changed everything into for loops this is what C# would see:

class Program
{

    static void Main(string[] args)
    {
        List<string> names = new List<string>() { "A", "B", "C" };

        List<Action> actions = new List<Action>();

        string name = names[0];

        Action test = () => Console.WriteLine(name);

        for (int i = 0; i < names.Count; i++)
        {
            actions.Add(test);
        }

        name = names[1];
        foreach (var action in actions)
        {
            action.Invoke(); // Prints "B" every time because name = names[1]
        }

        Console.ReadLine();
    }
}
like image 41
nerdybeardo Avatar answered Oct 07 '22 03:10

nerdybeardo