2015-08-25 20 views
5

Stavo cercando di ottenere alcuni elenchi ordinati utilizzando OrderBy all'interno di un ciclo foreach, ma per qualche motivo non stavano mantenendo il loro ordinamento al di fuori del ciclo. Ecco un po 'di codice e commenti per evidenziare quello che stava accadendo semplificata:Elenco <T> vs IEnumerable <T> in foreach

public class Parent 
{ 
    // Other properties... 
    public IList<Child> Children { get; set; } 
} 

public IEnumerable<Parent> DoStuff() 
{ 
    var result = DoOtherStuff() // Returns IEnumerable<Parent> 
     .OrderByDescending(SomePredicate) 
     .ThenBy(AnotherPredicate); // This sorting works as expected in the return value. 

    foreach (Parent parent in result) 
    { 
     parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList(); 
     // When I look at parent.Children here in the debugger, it's sorted properly. 
    } 

    return result; 
    // When I look at the return value, the Children are not sorted. 
} 

Tuttavia, quando invece assegnare result come questo:

var result = DoOtherStuff() 
    .OrderByDescending(SomePredicate) 
    .ThenBy(AnotherPredicate) 
    .ToList(); // <-- Added ToList here 

quindi il valore di ritorno ha i bambini smaltiti correttamente in ciascuna delle i genitori.

Qual è il comportamento di List<T> rispetto a IEnumerable<T> in un ciclo foreach?

Sembra esserci una sorta di differenza dal momento che trasformare i risultati in un elenco ha risolto i problemi con l'ordinamento nel ciclo foreach. Si si sente come il primo snippet di codice crea un iteratore che crea una copia di ogni elemento quando si esegue l'iterazione con foreach (e quindi le mie modifiche vengono applicate alla copia ma non l'oggetto originale in result) mentre si utilizzava ToList() l'enumeratore invece fornisce un puntatore.

Cosa sta succedendo qui?

risposta

-1

Osservazioni

Il ToList (IEnumerable) Metodo forze immediato valutazione query e restituisce un elenco contenente la query risultati. È possibile aggiungere questo metodo alla query per ottenere una copia memorizzata nella cache dei risultati dell'interrogazione.

Da: https://msdn.microsoft.com/en-us/library/bb342261(v=vs.110).aspx

Se si omette il ToList() la query non viene valutata ... Il vostro debugger potrebbe farlo per voi, ma che è solo una supposizione di me.

6

La differenza è che uno è un'espressione che può generare un set di oggetti Parent e l'altro è un elenco di oggetti Parent.

Ogni volta che si utilizza l'espressione, utilizzerà il risultato originale da DoOtherStuff e quindi li ordina. Nel tuo caso significa che creerà un nuovo set di oggetti Parent (poiché ovviamente non conservano i bambini dall'uso precedente).

Ciò significa che quando si collegano gli oggetti e si ordinano i bambini, tali oggetti verranno gettati via. Quando si utilizza nuovamente l'espressione per restituire il risultato, verrà creato un nuovo insieme di oggetti in cui i bambini sono naturalmente nell'ordine originale.

+0

@ user2864740: Capisco cosa intendi. Ho corretto la risposta. – Guffa

+0

Questo è corretto .... Facilmente controllato inserendo un punto di interruzione in DoOtherStuff(). Ogni volta che si scorre l'enumeratore (IEnumerable ) verrà chiamato DoOtherStuff. Call ToList() esegue l'enumeratore e posiziona il risultato in un elenco. L'iterazione nell'elenco non colpirà il punto di interruzione in DoOtherStuff() – Mick

+0

@Mick: il metodo 'DoOtherStuff' non verrà chiamato di nuovo, ma il codice all'interno del metodo potrebbe essere nuovamente eseguito. Il metodo restituisce un enumeratore e quando viene ricreato il codice che lo ha creato verrà eseguito nuovamente. – Guffa

1

codice di esempio di ciò che probabilmente accade quello di aggiungere Guffa's answer:

class Parent { public List<string> Children; } 

Enumerable di "Parent", creerà nuova "Parent" oggetti ogni volta viene iterato:

var result = Enumerable.Range(0, 10) 
     .Select(_ => new Parent { Children = new List<sting>{"b", "a"}); 

Ora con la prima iterazione con foreach verranno creati 10 oggetti "Parent" (uno per ogni iterazione del ciclo) e scartati immediatamente alla fine di ogni iterazione:

foreach (Parent parent in result) 
{ 
    // sorts children of just created parent object 
    parent.Children = parent.Children.OrderBy(YetAnotherPredicate).ToList(); 

    // parent is no longer referenced by anything - discarded and eligible for GC 
} 

Quando si guarda a result ancora una volta sarà ribadita e la nuova serie di oggetti "genitore" creato ogni volta si guardi, da cui "I bambini" non sono ordinati.

Si noti che a seconda di come è implementato DoOtherStuff() // Returns IEnumerable<Parent> il risultato potrebbe essere diverso. Cioè DoOtherStuff() può tornare insieme di elementi esistenti da qualche collezione cache:

List<Parent> allMyParents = ...; 

IEnumerable<Parent> DoOtherStuff() 
{ 
     return allMyParents.Take(7); 
} 

Ora ogni iterazione del result vi darà nuova collezione, ma ogni elemento della collezione sarà solo un elemento da allMyParents lista - in modo modifica "Bambini" la proprietà cambierebbe le istanze in allMyParents e il cambiamento si attiverebbe.

Problemi correlati