The scope of Linq expressions defined in a loop - Problem: closing a loop variable

I have a space question about Linq expressions that are defined in a loop. The following LinqPad C # program demonstrates behavior:

void Main() { string[] data=new string[] {"A1", "B1", "A2", "B2" }; string[] keys=new string[] {"A", "B" }; List<Result> results=new List<Result>(); foreach (string key in keys) { IEnumerable<string> myData=data.Where (x => x.StartsWith(key)); results.Add(new Result() { Key=key, Data=myData}); } results.Dump(); } // Define other methods and classes here class Result { public string Key { get; set; } public IEnumerable<string> Data { get; set; } } 

In principle, โ€œAโ€ should have data [A1, A2] and โ€œBโ€ should have data [B1, B2].

However, when starting, โ€œAโ€ gets the data [B1, B2], just like BIe, the last expression is evaluated for all instances of the result.

Given that I declared "myData" in the loop, why does it behave as if I declared it outside the loop? EG works as I would expect if I did this:

 void Main() { string[] data=new string[] {"A1", "B1", "A2", "B2" }; string[] keys=new string[] {"A", "B" }; List<Result> results=new List<Result>(); IEnumerable<string> myData; foreach (string key in keys) { myData=data.Where (x => x.StartsWith(key)); results.Add(new Result() { Key=key, Data=myData}); } results.Dump(); } // Define other methods and classes here class Result { public string Key { get; set; } public IEnumerable<string> Data { get; set; } } 

I get the desired result, if I force the evaluation inside the iteration, this is not my question.

I ask why "myData" seems to be split between iterations, given that I declared it as part of a single iteration?

Someone is calling Jon Skeet ...; ^)

+2
source share
1 answer

This is not myData , which is shared - it is key . And since the values โ€‹โ€‹in myData are evaluated lazily, they depend on the current value of key .

This behaves this way because the scope of the iteration variable is the whole loop, and not each iteration of the loop. You have one key variable whose value changes, and this is a variable that is captured by the lambda expression.

The correct fix is โ€‹โ€‹to simply copy the iteration variable into the variable in the loop:

 foreach (string key in keys) { String keyCopy = key; IEnumerable<string> myData = data.Where (x => x.StartsWith(keyCopy)); results.Add(new Result() { Key = key, Data = myData}); } 

For more information about this issue, see Eric Lippert's blog post, โ€œClosing a loop variable that is considered harmfulโ€: Part One , Part Two .

This is an unfortunate artifact of how the language was developed, but now changing it will be a bad IMO idea. Although any code that changed the behavior would basically be aborted in advance, this would mean that the correct code in (say) C # 6 would be valid, but the wrong code in C # 5 and that a dangerous position would be found.

+5
source

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


All Articles