2015-06-18 34 views
5

Czy ktoś może mi wyjaśnić, dlaczego następujące kwerendy LINQ zgłasza wyjątek InvalidOperationException?
(nie mów, że lista nie zawiera żadnych elementów, wartość, że szukam zawsze istnieje w kolekcji)Sekwencja nie zawiera elementów

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; 
    } 
} 

Problem pojawia się tylko wtedy, gdy zadzwonię NewMethod wewnątrz mojego lambda wyrażają dynamiczne.
Jeśli to zrobić to działa

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

Dodałem pole lastNumber aby pomóc debugowania kodu, można zobaczyć, że wartość istnieje w kolekcji kiedy się zawiesi

PS
Problemem nie jest zmienna losowa, usunąłem parametr i utworzyć nowy lokalny losowo wewnątrz metody, ale problem nadal istnieje

Aktualizacja

okazuje że nie potrzebujesz pętli, aby spowodować awarię. Jeśli uruchomić program wiele razy dostaniesz błędu ponownie

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; 
     } 
} 
+0

O ile wiem, nie możesz użyj "złożonej" metody w wyrażeniu lamda, tylko rzeczy, które można przekonwertować na instrukcje. http://stackoverflow.com/questions/1883920/call-a-function-for-each-value-in-a-eneric-c-sharp-collection –

+1

@ZivWeissman Możesz nazwać metody "złożone" - to pytanie mówienie o metodach z efektami ubocznymi_. –

Odpowiedz

5

@Oleg ma rację, ale tu dlaczego to jest problem.

Where skanuje listę szukając elementów pasujących do podanych kryteriów. W tym przypadku zmiany kryteriów dla każdego elementu. Jeśli wprowadzono problem mniejszy, powiedzmy tablica 5 elementów:

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

A potem przelotowe szuka wartości, która odpowiada pewnej liczby losowej (X jest Item[i] i r to liczba losowa):

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 

Należy pamiętać, że żaden przedmiot nie pasuje do tej konkretnej liczby losowej, więc żaden element nie spełniał kryteriów, a First zgłasza wyjątek!

poprawkę, jak odkryliśmy, jest generowanie liczb losowych przed wyliczenie:

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

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

Notatka:

To trochę mylące użyć maxValue tutaj:

Enumerable.Range(minValue, maxValue) 

Ponieważ drugi parametr to Enumerable.Range to długość wynikowej kolekcji, a nie wartość maksymalna.W tym przypadku działa, ponieważ zaczynasz od 1, ale wyniki byłyby nieoczekiwane, gdybyś użył, powiedzmy 99 i 100, by uzyskać zakres od 99 do 198.

+0

Nie mogę uwierzyć, że byłem tak głupi !!! Myślę, że muszę iść spać !!! –

+0

@GeorgeVovos Nie czuj się źle - musiałem sam to zasymulować, aby zobaczyć problem! –

4

To dlatego NewMethod powołuje się na każdej iteracji i za każdym razem generuje nową liczbę losową.

Ale w tym kodzie na początku generuje numer, a następnie porównuje do każdego elementu zbierania liczb.

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

Nie jestem pewien, że rozumiem, że nowa wygenerowana liczba zawsze istnieje w kolekcji. Dlaczego to się psuje? –

+0

To prawda, że ​​liczba zawsze istnieje w całym zbiorze, ale ta liczba nie jest równa bieżącej liczbie, na której testowana jest równość. – Oleg

+0

ooo, widzę, co masz na myśli, NewMethod jest wywoływany w środku, gdzie wiele razy, kiedy powiedziałeś iterację myślałem, że pętla for –