How to use linq in a loop?

Let's start with some input data for the query:

int[] someData = { 1, 2 }; 

After running the following code, everything works as I expect: a contains 2 elements, which are reduced to 1 and 2 , pulled from someData .

 List<IEnumerable<int>> a = new List<IEnumerable<int>>(); a.Add(someData.Where(n => n == 1)); a.Add(someData.Where(n => n == 2)); 

But this following code, which does the same thing only in a loop, does not work properly. When this code completes, b contains 2 elements, but they are both identical - pointing to 2 . In the second loop, it changes the first element of b .

 List<IEnumerable<int>> b = new List<IEnumerable<int>>(); for (int i = 1; i <= 2; ++i) { b.Add(someData.Where(n => n == i)); } 

Why is this happening and how can I make the loop version behave like the first version?

+4
source share
4 answers

Jon Skeet has a nice answer here.

You need to assign i to temp variable and use it in Linq query

 List<IEnumerable<int>> b = new List<IEnumerable<int>>(); for (int i = 1; i <= 2; ++i) { int temp = i; b.Add(someData.Where(n => n == temp)); } 
+7
source

Your problem is a lazy assessment. You add Enumerable to b , which represents someData.Where(n => n == i) . This is evaluated whenever you look at element b with value i at this time.

You want to show the enumerable by calling ToArray() or ToList() on it.

 for (int i = 1; i <= 2; ++i) { b.Add(someData.Where(n => n == i).ToArray()); } 

Alternatively, you can reduce the scope of the captured variable:

 for (int i = 1; i <= 2; ++i) { int localI=i; b.Add(someData.Where(n => n == localI)); } 

Then you still lazily evaluate the enums (which shows when you change someData ), but each has a different i .

+4
source

For each declaration of the where clause in the loop, there is a separate Func <int, bool> which is passed to it. Since the value I am passed to the lambda function associated with each instance of Func <int, bool> (Func <int, bool> is essentially a delegate), I am a captured variable that is shared by each instance of Func <int, bool>.

In other words, I have to be β€œleft alive”, even outside the scope of the loop, so that its value is evaluated whenever any instance of Func <int, bool> is called. This is usually not a problem, except that the call will not be made until it is needed (i.e., it is necessary to list the results of the request to which the delegate instance is passed. Example: this is a foreach: only loop, then the delegate will be called, to determine the results of a query for iteration purposes).

This means that LINQ queries declared in the loop will not actually be executed until some time after the for loop completes, which means that I will be set to 2. As a result, you are actually doing something like this:

  b.Add(someData.Where(n => n == 2)); b.Add(someData.Where(n => n == 2)); 

To avoid this, for each iteration in the loop you need to declare a separate instance of the integer type and make it equivalent to i. Pass this to the lambda function declared in the iteration, and each Func <int, bool> instance will have a separate captured variable, the value of which will not be changed after each subsequent iteration. For instance:

  for (int i = 1; i <= 2; ++i) { int j = i; b.Add(someData.Where(n => n == j)); } 
+3
source

This is a slightly hidden variation of the captured-var loop problem.

You can solve it as follows:

 List<IEnumerable<int>> b = new List<IEnumerable<int>>(); for (int i = 1; i <= 2; ++i) { int j = i; // essential b.Add(someData.Where(n => n == j)); } 

But a little more intuitive would be

 List<IEnumerable<int>> b = new List<IEnumerable<int>>(); for (int i = 1; i <= 2; ++i) { b.Add(someData.Where(n => n == i).ToList()); } 

What happens in your source code is that the variable i captured (closed) and the link is stored in lambdas. And you got an IEnumerable result that delays the execution of menas. The value i is displayed only when displaying / viewing the results, and by that time it is 2 .

+1
source

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


All Articles