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();
}
}