little_stone_05
little_stone_05

Reputation: 155

Loop through IEnumerable returns nothing

I am trying to return an element in a List (lookupData) that matches a specific criteria (as defined by the elements in the lookupKey array).

However, no result is returned if I define the output as IEnumerable type. The 'Output' variable resets at the final loop when i = 2 . Why does it not work?

I want to keep the output varaible as IEnumerable instead of List as it is more efficient.

   var lookupKey = new string[] { "Male", "China" };

   var lookupData = new List<string[]>();
   lookupData.Add(new string[] { "Male", "China" });
   lookupData.Add(new string[] { "Male", "America" });
   lookupData.Add(new string[] { "Female", "UK" });

   IEnumerable<string[]> output = lookupData;

   for (int i = 0; i < 2; i++)
   {
      output = output.Where(x => x[i] == lookupKey[i]);
   }

Upvotes: 1

Views: 822

Answers (5)

Caius Jard
Caius Jard

Reputation: 74595

In simple terms, when you use a variable in a lambda it extends the scope of the variable to the lambda/the lambda still has access to i even after the for loop is over and your reference to i, established in the for loop initializer, has gone out of scope.

Because LINQ execution of the lambda only actually happens when you request the result/enumerate the enumerable (and this could be hours later) the lambda will see the value of i as it was left by the for loop, i.e. 2 and this will mean your lambda experiences an index out of range

In recent versions of c# the internal behavior of a foreach loop was modified so that each iteration of the loop returns a copy of the variable from whatever was being iterated, so you should be able to change your for into a foreach on lookupKey and it will work as you expect. It's an awkward pattern to read and understand though, and I think you should consider changing it for something like:

var output = lookupData.Where(arr => arr.SequenceEquals(lookupKey));

If the dataset will be large and searched often, consider using a container that will hash the items instead because right now this method requires a large number of string comparisons - the number of entries in lookupData multiplied by the number of entries in lookupKey

Upvotes: 1

RoadRunner
RoadRunner

Reputation: 26315

You could use Where(), All() and Contains() from LINQ:

var output = lookupData
    .Where(data => data.All(item => lookupKey.Contains(item)))

Note: if you want your result to be a List<string[]> instead of IEnumerable<string[]>, add ToList() at the end of the query.

Upvotes: 0

Karan
Karan

Reputation: 12619

Use List<string[]> instead of IEnumerable<string[]>. As IEnumerable<string[]> will execute query when it needs to get values (ex. Count() or Loop). So in your case after for loop of your condition when you try to loop over IEnumerable object at that moment it will evaluate .Where(x => x[i] == lookupKey[i]). So here it will have i = 3. This is cause of your issue.

If you use List<string[]> then it will evaluate expression immediately inside the for loop that you have used.

Your updated code will look like below.

var lookupKey = new string[] { "Male", "China" };

var lookupData = new List<string[]>();
lookupData.Add(new string[] { "Male", "China" });
lookupData.Add(new string[] { "Male", "America" });
lookupData.Add(new string[] { "Female", "UK" });

List<string[]> output = lookupData;

for (int i = 0; i < 2; i++)
{
   output = output.Where(x => x[i] == lookupKey[i]).ToList();
}

IEnumerable vs List - What to Use? How do they work? Hopefully this answer might be helpful.

Upvotes: 0

Theodor Zoulias
Theodor Zoulias

Reputation: 43390

The i loop variable is not captured the way you intend. The solution is to introduce a local variable inside the loop:

for (int i = 0; i < 2; i++)
{
    var index = i;
    output = output.Where(x => x[index] == lookupKey[index]);
}

Look here for more info about capturing loop variables: Captured variable in a loop in C#

Upvotes: 2

Roman.Pavelko
Roman.Pavelko

Reputation: 1655

You search logic is not quite right, you don't need for loop as it overrides where criteria. Here is a possible way of search, it will return IEnumerable<string[]>:

var output = lookupData.Where(g => g[0] == lookupKey[0] && g[1] == lookupKey[1]);

Upvotes: 0

Related Questions