Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid variable capturing in lambda expression?

Tags:

c#

lambda


Lately I've read C# in Depth and it taught me about lambda expression, I've been using them to pas data to click events and such like:

image.MouseDown+=(o ,e)=>MethodToDoSomething(DataNeededForAction);

now the problem with those is variable capturing when used in a foreach loop (thank you Jon Skeet for making that part really clear :), when initializing several objects that have events that I subscribe to I usually bump into an issue with variable capturing. consider the following example:

foreach (var game in GamesCollection)
{
    Image img = new Image();
    img.Width = 100;
    img.MouseDown+=(o,e) => MyMethod(game.id);
}

To avoid capturing in this situation I have to add some variable to assign game to and then pass that variable to the method, this creates extra unclear code and mostly, additional clutter. Is there a way to bypass this? Something that'll at least look cleaner?

Thx, Ziv

like image 255
Ziv Avatar asked Apr 06 '11 22:04

Ziv


People also ask

How are variables used outside of lambda expression?

Because the local variables declared outside the lambda expression can be final or effectively final. The rule of final or effectively final is also applicable for method parameters and exception parameters. The this and super references inside a lambda expression body are the same as their enclosing scope.

How resolve variable used in lambda expression should be final or effectively final?

The local variables that a lambda expression may use are referred to as “effectively final”. An effectively final variable is one whose value doesn't change after it's first assigned. There is no need to explicitly declare such a variable as final, although doing so would not be an error.

How do you capture a variable in lambda function?

Much like functions can change the value of arguments passed by reference, we can also capture variables by reference to allow our lambda to affect the value of the argument. To capture a variable by reference, we prepend an ampersand ( & ) to the variable name in the capture.

Can we use Non final local variables inside a lambda expression?

Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final , or a compile-time error occurs where the use is attempted.


2 Answers

(EDIT: Note that this has changed with C# 5, where foreach now effectively creates a fresh variable for each iteration.)

No, there's no way of avoiding this. Basically it was a mistake in the way the language specification described foreach in terms of a single variable, rather than a new variable per iteration. Personally I don't see it as a problem in terms of the amount of code involved - by the time you've realised that it's a problem and figured out how to fix it, you've got over the biggest hurdle. I'd normally include a comment to make it obvious to maintenance programmers, mind you.

I'm sure the C# team would do it differently if they were starting from scratch, but they're not. Heck, they've even discussed changing the existing behaviour... but there are good reasons against that change (notably that code which works correctly on a C# N+1 compiler would still compile, but give the wrong result on a C# N compiler; a very subtle source of bugs for open source library authors who expect their code to be built with multiple compilers).

(Hope you're enjoying the book, btw...)

like image 186
Jon Skeet Avatar answered Oct 11 '22 07:10

Jon Skeet


In short, no. You need to create extra storage to keep the correct instance of game available in the method closure.

like image 43
spender Avatar answered Oct 11 '22 08:10

spender