1

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]);
   }
ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
little_stone_05
  • 145
  • 1
  • 9
  • Can we just confirm that you want to get the output only if x[0] == lookupKey[0] && x[1] == lookupKey[1] (which is sort of what your code currently does)? – ProgrammingLlama Jan 30 '20 at 05:17
  • What does "the output variable resets" mean? – Caius Jard Jan 30 '20 at 05:33
  • In the example you have provided the first iteration matches and subsequent loops do not. If the output is found then it should be returned. – timkly Jan 30 '20 at 05:39
  • Actually, the lookup key does not have an array that would meet the requirements of i being greater than 0. – timkly Jan 30 '20 at 05:48

5 Answers5

2

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#

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
1

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

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
0

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.

RoadRunner
  • 25,803
  • 6
  • 42
  • 75
0

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]);
Roman.Pavelko
  • 1,555
  • 2
  • 15
  • 18
  • The for loop is to go through each element in the array. This is an simplified example where there are 2 indexes in the criteria. We can extend it to n cases. – little_stone_05 Jan 30 '20 at 10:15
0

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.

Karan
  • 12,059
  • 3
  • 24
  • 40
  • This has a lot of waste especially if the op is giving us a simplified example, it would be better to just capture the variable – johnny 5 Jan 30 '20 at 07:34
  • I agree, Capturing variable would be better solution as one of the answer from @Theodor Zoulias. I just keep it simple and explained why OP didn't got desired result. My answer could be alternate solution and it will be upto OP and requirement based on which one can decide what solution to use. – Karan Jan 30 '20 at 08:17