5

Can someone explain me why the following LINQ query throws an InvalidOperationException?
(Don't say that the list has no elements,the value that I'm looking for always exists in the collection)

class Program
{
     static int lastNumber;
     static void Main()
     {
          int loopCount = 100; int minValue = 1; int maxValue = 10000;
          var numbers = Enumerable.Range(minValue, maxValue).ToList();//or ToArray();
          Random random = new Random();

          for (int i = 0; i < loopCount; i++)
          {
              //.First() throws the exception but it is obvious that the value exists in the list
              int x = numbers.Where(v => v == NewMethod(minValue, maxValue, random)).First();
          }
          Console.WriteLine("Finished");
          Console.ReadLine();

     }

     private static int NewMethod(int minValue, int maxValue, Random random)
     {
         var a1 = random.Next(minValue + 1, maxValue - 1);
         lastNumber = a1;
         return a1;
     }
}

The problem appears only when I call NewMethod inside my lambda expession.
If do this it works

int temp=NewMethod(minValue, maxValue, random);
int x = numbers.Where(v => v == temp).First();

I added the lastNumber field to help debug the code,you can see that the value exists in the collection when it crashes

PS
The problem is not the random variable,I removed the parameter and create a new local random inside the method but the problem still exists

Update

It turns out that you don't need the loop to make it crash. If you run the program many times you will get the error again

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
class Program
{
    static int lastNumber;
    static void Main()
    {
        int minValue = 1, maxValue = 100000;
        var numbers = Enumerable.Range(minValue, maxValue).ToArray();
        //Crashes sometimes
        int x = numbers.Where(v => v == NewMethod(minValue, maxValue)).First();
        Console.WriteLine("Finished");
        Console.ReadLine();
     }

     private static int NewMethod(int minValue, int maxValue)
     {
          Random random = new Random();
          var a1 = random.Next(minValue + 1, maxValue - 1);
          lastNumber = a1;
          return a1;
      }
 }
George Vovos
  • 7,563
  • 2
  • 22
  • 45
  • As far as i know you can't use a "complex" method inside lamda expression, only things that can be converted to statements. http://stackoverflow.com/questions/1883920/call-a-function-for-each-value-in-a-generic-c-sharp-collection – Ziv Weissman Jun 18 '15 at 22:02
  • 1
    @ZivWeissman You can call "complex" methods - that question was talking about methods _with side effects_. – D Stanley Jun 18 '15 at 22:05

2 Answers2

5

@Oleg is right, but here's why it's the problem.

Where scans through the list looking for elements that match the given criteria. In this case the criteria changes for each element. If you made the problem smaller, say an array of 5 elements:

List<Int32> (5 items)
1 
2 
3 
4 
5 

And then looped through looking for a value that matches some random number (x is Item[i] and r is a random number):

Item 1: x = 1, r = 2  // fail
Item 2: x = 2, r = 3  // fail
Item 3: x = 3, r = 2  // fail
Item 4: x = 4, r = 3  // fail
Item 5: x = 5, r = 2  // fail

Note that no item match that particular random number, so no item matched the criteria and First throws an exception!

The fix, as you have discovered, is to generate the random number before the enumeration:

int temp=NewMethod(minValue, maxValue, random);  // say 2

Item 1: x = 1, temp = 2  // fail
Item 2: x = 2, temp = 2  // success!

Side note:

It's a bit misleading to use maxValue here:

 Enumerable.Range(minValue, maxValue)

Since the second parameter to Enumerable.Range is the length of the resulting collection, not the max value. In this case it works because you're starting at 1 but the results would be unexpected if you used, say 99 and 100 - you'd get a range from 99 to 198.

D Stanley
  • 149,601
  • 11
  • 178
  • 240
4

It's because NewMethod invokes on every iteration and every time generates new random number.

But in this code at first generates number and then compares to each element of numbers collection.

int temp=NewMethod(minValue, maxValue, random);
int x = numbers.Where(v => v == temp).First();
Oleg
  • 1,378
  • 11
  • 22
  • 1
    Not sure I understand,the new generated number always exists in the collection.Why does it crash? – George Vovos Jun 18 '15 at 22:10
  • That's true that number alway exists in entire collection, but this number is not equal to current number on which test equality. – Oleg Jun 18 '15 at 22:12
  • ooo,I see what you mean,NewMethod is invoked inside where many times,when you said iteration i thought the for loop – George Vovos Jun 18 '15 at 22:40