2012-02-09 14 views
6

IEnumerable non garantisce che l'enumerazione due volte produrrà lo stesso risultato. In realtà, è abbastanza facile creare un esempio in cui myEnumerable.First() restituisce valori diversi quando eseguito due volte:Esiste un modo canonico per "risolvere" un IEnumerable "dinamico"?

class A { 
    public A(string value) { Value = value; } 
    public string Value {get; set; } 
} 

static IEnumerable<A> getFixedIEnumerable() { 
    return new A[] { new A("Hello"), new A("World") }; 
} 

static IEnumerable<A> getDynamicIEnumerable() { 
    yield return new A("Hello"); 
    yield return new A("World"); 
} 

static void Main(string[] args) 
{ 
    IEnumerable<A> fix = getFixedIEnumerable(); 
    IEnumerable<A> dyn = getDynamicIEnumerable(); 

    Console.WriteLine(fix.First() == fix.First()); // true 
    Console.WriteLine(dyn.First() == dyn.First()); // false 
} 

Questo non è solo un esempio accademico: Utilizzando il popolare from ... in ... select new A(...) creerà esattamente questa situazione. Questo può portare a un comportamento imprevisto:

fix.First().Value = "NEW"; 
Console.WriteLine(fix.First().Value); // prints NEW 

dyn.First().Value = "NEW"; 
Console.WriteLine(dyn.First().Value); // prints Hello 

capisco perché questo accade. So anche che questo problema potrebbe essere risolto eseguendo ToList() sull'enumerabile o ignorando == per la classe A. Questa non è la mia domanda.

La domanda è: quando si scrive un metodo che accetta un IEnumerable arbitrario e si desidera la proprietà che la sequenza viene valutata solo una volta (e quindi i riferimenti sono "corretti"), qual è il modo canonico per fare ciò? ToList() sembra essere usato principalmente, ma se la fonte è già stata riparata (ad esempio, se l'origine è una matrice), i riferimenti vengono copiati in una lista (inutilmente, poiché tutto ciò di cui ho bisogno è la proprietà fissa). C'è qualcosa di più adatto o è ToList() la soluzione "canonica" per questo problema?

+1

Per definizione un IEnumerable non dice che l'ordine non è garantito? Se l'ordine è richiesto per la tua implementazione stai utilizzando il tipo di dati sbagliato, no? Tutto quello che chiedi nel tuo contratto con IEnumerable è che gli elementi possono essere attraversati. IE: come dici tu, un elenco è più adatto. –

risposta

3

ToList è decisamente la strada da percorrere.

Ha alcune ottimizzazioni: se l'enumerazione ingresso è un ICollection (ad esempio, un array o lista) si chiama ICollection.CopyTo per copiare la raccolta di una serie piuttosto che realmente enumerare - quindi il costo è improbabile che sia significativo a meno che la collezione è enorme

IMHO, in generale, è meglio per la maggior parte dei metodi per restituire ICollection<T> o IList<T> piuttosto che IEnumerable<T>, a meno che non si vuole suggerire ai consumatori del metodo che l'attuazione può utilizzare la valutazione pigra (ad esempio resa).

Nei casi in cui un metodo deve restituire una lista immutabile, restituire un involucro sola lettura (ReadOnlyCollection<T>) esempio chiamando ToList().AsReadOnly(), ma restituisce ancora il tipo di interfaccia IList<T>.

Se si segue questa linea guida, i consumatori del metodo non avranno mai bisogno di una chiamata non necessaria a ToList.

2

Vuoi dire se la funzione accetta un parametro e si desidera assicurarsi che il parametro non è pigro, perché è necessario scorrere su di esso più di una volta nel metodo? Generalmente, in questo caso, il parametro diventa ICollection, pertanto è responsabilità del chiamante reificare l'enumerazione se è pigro.

0

Dal modo in cui hai spiegato alla tua domanda, sembra che si desidera enumerare una sequenza più volte e ottenere lo stesso ordine ogni volta. Ciò implica che stai mantenendo un riferimento alla sequenza per eseguire le iterazioni in un secondo momento, oppure stai facendo confronti più volte nello stesso metodo.

In genere, quando si accetta uno IEnumerable<T> che si intende mantenere un riferimento o manipolare più di una volta, è più sicuro copiare la sequenza in una rappresentazione interna. La mia preferenza è usare l'estensione ToList(), in quanto è molto ben compresa e probabilmente la raccolta più semplice con un ordine prevedibile.

Internamente, .NET Framework tende a utilizzare List<T> ogni volta che è necessaria una raccolta "generale".

1

Il problema più comune è dichiarato da Joel Spolsky nel suo Law of Leaky Abstractions. Avendo il parametro IEnumerable nel tuo metodo ti aspetti che un oggetto possa essere semplicemente enumerato e nient'altro. Tuttavia, tuttavia, si cura della raccolta passata sia che sia "fissa" o "dinamica". Puoi vedere che l'astrazione fatta da IEnumerable è trapelata. Non esiste una soluzione che si adatta bene a tutti i casi. Nel tuo caso puoi passare List<T> o T[] (o altri tipi che ti aspetti siano tipi effettivi per parametri nel tuo metodo) invece di IEnumerable. Il consiglio più comune è realizzare la tua astrazione e progettare il tuo codice rispetto ad esso.

2

L'interfaccia IEnumerable fornisce semplicemente "un modo" per scorrere gli elementi (la creazione di un oggetto IEnumerable da una query di linq è un esempio perfetto come IEnumerable è come una query SQL per un database). Sarà sempre dinamico finché non si memorizza il risultato in un ICollection (ToList, ToArray), quindi l'iterazione viene elaborata fino alla fine e il risultato viene archiviato in modo "fisso".

Problemi correlati