views:

67

answers:

1

I have a piece of code that filters a list using LINQ, creates a list of instances of an anonymous type, and assigns an event handler to each instance:

// Select every linear expression and create a menu item from it
var items = from expr in expressionList.Expressions
            where expr.Type == ExpressionType.Linear
            let stdExpr = (StandardExpression)expr
            select new
            {
                Menu = new ToolStripMenuItem(stdExpr.Expression), // string
                stdExpr.Slot // int
            };

// Wire a Click event handler to each menu to set the tracked line
foreach (var item in items)
{
    item.Menu.Click += (s, e) => graph.SetTrackedLine(item.Slot);

    menuTrackLineWithMouse.DropDownItems.Add(item.Menu);
}

This works well in that the event handlers get wired and the menus get added correctly. The problem comes when the menu item is clicked, and the handler is fired. No matter what menu item fired the handler, only the last one is ever passed to SetTrackedLine.

An example is if I have two menus, "sin(x)", with slot 0, and "cos(x)", with slot 1, both Click events pass 1 to SetTrackedLine, no matter if "sin(x)" was clicked or "cos(x)" was.

My question is, why does this happen? Shouldn't item.Slot refer to each separate instance of the anonymous type?

Thanks.

+9  A: 

You are closing over the loop variable. The problem specifically is here:

(s, e) => graph.SetTrackedLine(item.Slot)
                               ^^^^

The value of item used will be the current value when the lambda expression is run, not the value it had when it was created. This is a "gotcha" of C# and a common error.

Try this instead:

foreach (var item in items)
{
    var item2 = item;
    item2.Menu.Click += (s, e) => graph.SetTrackedLine(item2.Slot);
    menuTrackLineWithMouse.DropDownItems.Add(item2.Menu);
}
Mark Byers
Ah, that clears it up, and that article was also an interesting read. Thank you very much!
nasufara