How to avoid capturing variables in a lambda expression?


Recently, I read C # in Depth, and he taught me lambda expression, I used them for pas data for event clicks, etc., for example:

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

Now the problem with these variables is captured when used in the foreach loop (thanks to Jon Skeet for the fact that this part is really understandable :), when initializing several objects that have events that I sign, I usually encounter a problem with the catch variable. consider the following example:

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

In order to avoid capture in this situation, I have to add some variable to assign the game, and then pass this variable to the method, which creates extra fuzzy code and basically an extra mess. Is there any way around this? Something that at least looks cleaner?

thanks ziv

+4
source share
4 answers

(EDIT: note that this has changed from C # 5, where foreach now efficiently creates a new variable for each iteration.)

No, there is no way to avoid this. This was mainly a mistake in how the language specification described foreach in terms of a single variable, rather than a new variable for iteration. Personally, I don’t see it as a problem in terms of the amount of code - by the time you understood that it was a problem and figured out how to fix it, you had overcome the biggest obstacle. Keep in mind that I usually add a comment to make it obvious to maintenance programmers, mind you.

I'm sure the C # team will do it differently if they start from scratch, but that is not the case. Heck, they even discussed changing existing behavior ... but there are good reasons against this change (especially the code that works correctly on the C # N + 1 Compiler still compiles, but gives the wrong result on the C # N Compiler, a very subtle source errors for open-source library authors who expect their code to be built with multiple compilers).

(I hope you enjoy the book, by the way ...)

+5
source

In short, no. You need to create additional storage in order to save the correct game instance in method closure.

+2
source

If the GamesCollection is a List<T> , you can use the ForEach method:

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

But in general, no. And one additional variable is not really cluttered.

0
source

How about pulling game.id first with a LINQ expression. Pressing a call through .Aggregate () should create a new variable every time, because this is really a parameter of the method.

 GamesCollection.Select(game => game.id).Aggregate((_, id) => { Image img = new Image(); img.Width = 100; img.MouseDown+=(o,e) => MyMethod(id); return img; }); 
-1
source

Source: https://habr.com/ru/post/1347001/


All Articles