2010-04-29 11 views
25

Un esempio banale di un "infinito" IEnumerable sarebbeCome gestire un IEnumerable "infinito"?

IEnumerable<int> Numbers() { 
    int i=0; 
    while(true) { 
    yield return unchecked(i++); 
    } 
} 

lo so, che

foreach(int i in Numbers().Take(10)) { 
    Console.WriteLine(i); 
} 

e

var q = Numbers(); 
foreach(int i in q.Take(10)) { 
    Console.WriteLine(i); 
} 

entrambi bene il lavoro (e stampare il numero 0- 9).

Ma ci sono delle insidie ​​quando si copiano o gestiscono espressioni come q? Posso contare sul fatto che vengono sempre valutati "pigri"? C'è qualche pericolo nel produrre un loop infinito?

+1

Solo per essere pignoli, il tuo esempio "infinito" non genera un'eccezione quando i = Int32.MaxValue e tu fai i ++?Oppure passa a Int32.MinValue? Hmm ..... –

+0

Hai ragione. Probabilmente genererà un'eccezione di overflow ... La modifico. – Danvil

+0

Solo essere pignoli qui, il tuo punto è ancora imbattuto. :) Inoltre, ho provato e lo fa in loop a Int32.MinValue. No OverflowException, quindi era in effetti un ciclo infinito. –

risposta

8

Sì, è garantito che il codice sopra verrà eseguito pigramente. Mentre sembra (nel codice) come ci si ciclo per sempre, il tuo codice realtà produce qualcosa di simile:

IEnumerable<int> Numbers() 
{ 
    return new PrivateNumbersEnumerable(); 
} 

private class PrivateNumbersEnumerable : IEnumerable<int> 
{ 
    public IEnumerator<int> GetEnumerator() 
    { 
     return new PrivateNumbersEnumerator(); 
    } 
} 

private class PrivateNumbersEnumerator : IEnumerator<int> 
{ 
    private int i; 

    public bool MoveNext() { i++; return true; } 

    public int Current 
    { 
     get { return i; } 
    } 
} 

(Questo ovviamente non è esattamente ciò che verrà generato, dal momento che questo è abbastanza specifico per il tuo codice, ma è comunque simile e dovrebbe mostrarti perché sarà valutato pigramente).

18

Fintanto che chiamate solo metodi pigri e senza buffer, dovreste stare bene. Quindi Skip, Take, Select, ecc. Vanno bene. Tuttavia, Min, Count, OrderBy ecc. Impazzirebbero.

Può funzionare, ma è necessario essere prudenti. O iniettare un Take(somethingFinite) come misura di sicurezza (o qualche altro metodo di estensione personalizzato che genera un'eccezione dopo troppi dati).

Ad esempio:

public static IEnumerable<T> SanityCheck<T>(this IEnumerable<T> data, int max) { 
    int i = 0; 
    foreach(T item in data) { 
     if(++i >= max) throw new InvalidOperationException(); 
     yield return item; 
    } 
} 
+1

+1 Ho una risposta in competizione, ma devo solo dare un upvote per un metodo di estensione per IEnumerable chiamato * SanityCheck *. Il nome della migliore funzione di sempre. Devo implementarlo ... –

+0

+1 per indicare questo anche se l'OP in realtà chiama 'foreach (int i in Numbers()) 'Questo potrebbe anche causare problemi. – Felype

4

Si dovrebbe evitare qualsiasi funzione avidi che tentano di leggere fino alla fine. Ciò include Enumerable estensioni come: Count, ToArray/ToList, e aggrega Avg/Min/Max, ecc

c'è niente di sbagliato con infinite liste pigro, ma è necessario prendere decisioni consapevoli su come gestirli.

Utilizzare Take per limitare l'impatto di un ciclo infinito impostando un limite superiore anche se non sono necessari tutti.

2

Sì, il tuo codice funzionerà sempre senza loop infinito. Qualcuno potrebbe venire anche se più tardi e fare casino. Supponiamo che vogliano fare:

var q = Numbers().ToList(); 

Quindi, tu hai il metro! Molte funzioni "aggregate" ti uccideranno, ad esempio Max().

0

Se non si trattava di una valutazione lazy, il primo esempio non funzionerà come previsto in primo luogo.