C#: Trouble with Lambdas in For Loops

I had an interesting bug the other day. I wrote a foreach loop along these lines:

foreach (var entry in controlsByName)

{

    entry.Key.SomeEvent += (sender,args)=>{ProcessControlName(entry.Value);}

}

Looks innocent enough, right? There is a big catch here. In functional languages we are accustomed to the fact that everything is immutable. I subconsciously transferred this notion to C# lambdas. I thought that the lambdas capture outer variables by value and carry this immutable value with them forever. This is wrong!.

C# language specification (found at C:\Program Files\Microsoft Visual Studio 9.0\VC#\Specifications if you have Visual Studio 2008) states in paragraph 7.14.4 that outer variables are captured by reference. They don’t use these exact words, but that’s the idea. If you change the value of the variable, all lambdas that captured it will be affected.

Even more surprisingly, there will be only one copy of the loop variable such as entry above, and all lambdas will share this single copy. This means that the code above has a bug. After the loop has finished, if SomeEvent is fired by one of the controls, the name passed to ProcessControlName() will always be the name of the last control, regardless of which control fired the event!

It turns out, you can even exploit this variable sharing making lambdas to communicate like this:

private void GetTangledLambdas(out Func<int> getter, out Action<int> setter)

{

    int x = 0;

    getter = () => x;

    setter = v => {x = v;};

}

 

[TestMethod]

public void Lambads_Can_Communicate_Through_Captured_Variable()

{

    Func<int> getter;

    Action<int> setter;

    GetTangledLambdas(out getter, out setter);

    setter(10);

    Assert.AreEqual(10, getter());

    setter(20);

    Assert.AreEqual(20, getter());

}

This, of course, is not even close to functional programming, even though it uses lambdas 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *