2010-05-05 12 views
142

Diciamo che avere una classeToList() - Crea una nuova lista?

public class MyObject 
{ 
    public int SimpleInt{get;set;} 
} 

e ho un List<MyObject>, e mi ToList() e quindi modificare uno dei SimpleInt, sarà il mio cambiamento essere propagato torna alla lista originale. In altre parole, quale sarebbe l'output del seguente metodo?

public void RunChangeList() 
{ 
    var objs = new List<MyObject>(){new MyObject(){SimpleInt=0}}; 
    var whatInt = ChangeToList(objs); 
} 
public int ChangeToList(List<MyObject> objects) 
{ 
    var objectList = objects.ToList(); 
    objectList[0].SimpleInt=5; 
    return objects[0].SimpleInt; 

} 

Perché?

P/S: Mi dispiace se sembra ovvio scoprirlo. Ma non ho compilatore con me ora ...

risposta

171

Sì, ToList creerà un nuovo elenco, ma poiché in questo caso MyObject è un tipo di riferimento, il nuovo elenco conterrà riferimenti agli stessi oggetti dell'elenco originale.

L'aggiornamento della proprietà SimpleInt di un oggetto a cui si fa riferimento nel nuovo elenco interesserà anche l'oggetto equivalente nell'elenco originale.

(Se MyObject è stato dichiarato come un struct piuttosto che un class poi il nuovo elenco conterrebbe copie degli elementi nella lista originale, e l'aggiornamento di una proprietà di un elemento nel nuovo elenco sarebbe non influenzare l'elemento equivalente nell'elenco originale.)

+1

Si noti inoltre che con un 'List' di strutture, un assegnamento come' objectList [0] .SimpleInt = 5' non sarebbe consentito (errore di compilazione C#). Questo perché il valore di ritorno dell'accessorio 'get' dell'indicizzatore di lista non è una variabile (è una copia restituita di un valore di struct), e quindi _setting_ il suo membro' .SimpleInt' con un'espressione di assegnazione non è permesso (sarebbe mutato una copia che non viene conservata). Bene, chi usa comunque le strutture mutevoli? –

+1

@Jeppe Stig Nielson: Sì. Analogamente, il compilatore smetterà anche di fare cose come 'foreach (var s in listOfStructs) {s.SimpleInt = 42; } '. Il gotcha _really_ nasty è quando si prova qualcosa come 'listOfStructs.ForEach (s => s.SimpleInt = 42)': il compilatore lo consente e il codice viene eseguito senza eccezioni, ma le strutture nell'elenco rimarranno invariate! – LukeH

29

ToList creerà sempre creare un nuovo elenco, che non rifletterà eventuali modifiche successive alla raccolta.

Tuttavia, rifletterà le modifiche agli oggetti stessi (a meno che non siano strutture modificabili).

In altre parole, se si sostituisce un oggetto nell'elenco originale con un oggetto diverso, ToList conterrà ancora il primo oggetto.
Tuttavia, se si modifica uno degli oggetti nell'elenco originale, lo ToList conterrà ancora lo stesso oggetto (modificato).

6

Viene creato un nuovo elenco ma gli elementi in esso contenuti sono riferimenti agli elementi originali (proprio come nell'elenco originale). Le modifiche alla lista stessa sono indipendenti, ma agli elementi troverai la modifica in entrambi gli elenchi.

10

Sì, crea una nuova lista. Questo è di design.

La lista conterrà gli stessi risultati come la sequenza enumerable originale, ma materializzato in una collezione persistente (in memoria). Ciò consente di consumare i risultati più volte senza incorrere nel costo di ricalcolare la sequenza.

La bellezza delle sequenze LINQ è che sono componibili. Spesso lo IEnumerable<T> si ottiene combinando più operazioni di filtraggio, ordinamento e/o proiezione. I metodi di estensione come ToList() e ToArray() consentono di convertire la sequenza calcolata in una raccolta standard.

2

ToList creerà un elenco nuovo di zecca.

Se gli elementi nell'elenco sono tipi di valore, verranno aggiornati direttamente, se si tratta di tipi di riferimento, eventuali modifiche verranno riflesse negli oggetti di riferimento.

1
var objectList = objects.ToList(); 
    objectList[0].SimpleInt=5; 

Ciò aggiornerà anche l'oggetto originale. Il nuovo elenco conterrà riferimenti agli oggetti contenuti al suo interno, proprio come l'elenco originale. Puoi anche modificare gli elementi e l'aggiornamento si rifletterà nell'altro.

Ora se si aggiorna un elenco (aggiunta o eliminazione di un elemento) che non si rifletterà nell'altro elenco.

4

Penso che questo è equivalente a chiedere se ToList fa una copia profonda o superficiale. Come ToList non ha alcun modo per clonare MyObject, si deve fare una copia, in modo che la lista creata contiene gli stessi riferimenti di quello originale, in modo che il codice restituisce 5.

55

Dalla fonte Reflector'd:

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source) 
{ 
    if (source == null) 
    { 
     throw Error.ArgumentNull("source"); 
    } 
    return new List<TSource>(source); 
} 

Quindi sì, l'elenco originale non verrà aggiornato (ad esempio aggiunte o rimozioni) tuttavia gli oggetti di riferimento lo faranno.

1

Non vedo da nessuna parte nella documentazione che ToList() sia sempre garantito per restituire un nuovo elenco. Se un oggetto IEnumerable è un elenco, potrebbe essere più efficiente verificarlo e restituire semplicemente la stessa lista.

La preoccupazione è che a volte potresti voler essere assolutamente sicuro che l'Elenco restituito sia! = Alla Lista originale. Poiché Microsoft non documenta che ToList restituirà una nuova lista, non possiamo essere sicuri (a meno che qualcuno non abbia trovato quella documentazione). Potrebbe anche cambiare in futuro, anche se ora funziona.

La nuova lista (Ienumerable enumerablestuff) è garantita per restituire una nuova lista. Lo userei invece.

+2

Lo dice nella documentazione qui: "Il metodo ToList (IEnumerable ) impone la valutazione immediata della query e restituisce un elenco che contiene i risultati dell'interrogazione.È possibile aggiungere questo metodo alla query per ottenere una copia memorizzata nella cache di i risultati della query.] (http://msdn.microsoft.com/en-us/library/bb342261.aspx) "La seconda frase ribadisce che qualsiasi modifica all'elenco originale dopo che la query è stata valutata non ha alcun effetto sull'elenco restituito . –

+1

@RaymondChen Questo vale per IEnumerables, ma non per Lists. Anche se 'ToList' sembra creare un nuovo riferimento a un elenco di oggetti quando viene chiamato su' List', BenB ha ragione quando dice che questo non è garantito dalla documentazione MS. – Teejay

+0

@RaymondChen Come per il sorgente ChrisS, 'IEnumerable <>. ToList()' è attualmente implementato come 'new List <> (source)' e non esiste un override specifico per 'List <>', quindi 'List <>. ToList() 'restituisce effettivamente un nuovo riferimento all'oggetto lista. Ma ancora una volta, come da documentazione MS, non c'è alcuna garanzia che questo non cambi in futuro, anche se probabilmente violare la maggior parte del codice. – Teejay

2

Nel caso in cui l'oggetto di origine sia un oggetto IEnumerable reale (vale a dire non solo una raccolta impacchettata come enumerabile), ToList() NON restituirà gli stessi riferimenti all'oggetto come nell'IEnumerable originale. Restituirà un nuovo Elenco di oggetti, ma quegli oggetti potrebbero non essere uguali o uguali agli oggetti restituiti da IEnumerable quando viene enumerato di nuovo

7

La risposta accettata risolve correttamente la domanda dell'OP in base al loro esempio. Tuttavia, si applica solo quando ToList viene applicato a una raccolta di calcestruzzo; non regge quando gli elementi della sequenza sorgente devono ancora essere istanziati (a causa dell'esecuzione posticipata). In caso di quest'ultimo, è possibile ottenere un nuovo set di elementi ogni volta che si chiama ToList (o enumerare la sequenza).

Ecco un adattamento del codice del PO per illustrare questo comportamento:

public static void RunChangeList() 
{ 
    var objs = Enumerable.Range(0, 10).Select(_ => new MyObject() { SimpleInt = 0 }); 
    var whatInt = ChangeToList(objs); // whatInt gets 0 
} 

public static int ChangeToList(IEnumerable<MyObject> objects) 
{ 
    var objectList = objects.ToList(); 
    objectList.First().SimpleInt = 5; 
    return objects.First().SimpleInt; 
} 

Mentre il codice sopra potrebbe apparire forzato, questo comportamento può apparire come una sottile problema in altri scenari. Vedere my other example per una situazione in cui provoca ripetutamente lo spawn delle attività.

4

Basta inciampare su questo vecchio post e ho pensato di aggiungere i miei due centesimi. In genere, se sono in dubbio, utilizzo rapidamente il metodo GetHashCode() su qualsiasi oggetto per controllare le identità.Così, per sopra -

public class MyObject 
{ 
    public int SimpleInt { get; set; } 
} 


class Program 
{ 

    public static void RunChangeList() 
    { 
     var objs = new List<MyObject>() { new MyObject() { SimpleInt = 0 } }; 
     Console.WriteLine("objs: {0}", objs.GetHashCode()); 
     Console.WriteLine("objs[0]: {0}", objs[0].GetHashCode()); 
     var whatInt = ChangeToList(objs); 
     Console.WriteLine("whatInt: {0}", whatInt.GetHashCode()); 
    } 

    public static int ChangeToList(List<MyObject> objects) 
    { 
     Console.WriteLine("objects: {0}", objects.GetHashCode()); 
     Console.WriteLine("objects[0]: {0}", objects[0].GetHashCode()); 
     var objectList = objects.ToList(); 
     Console.WriteLine("objectList: {0}", objectList.GetHashCode()); 
     Console.WriteLine("objectList[0]: {0}", objectList[0].GetHashCode()); 
     objectList[0].SimpleInt = 5; 
     return objects[0].SimpleInt; 

    } 

    private static void Main(string[] args) 
    { 
     RunChangeList(); 
     Console.ReadLine(); 
    } 

e risposta sulla mia macchina -

  • objs: 45653674
  • OBJS [0]: 41.149.443
  • oggetti: 45653674
  • oggetti [0]: 41.149.443
  • objectList: 39785641
  • objectList [0]: 41149443
  • whatInt: 5

Quindi in sostanza l'oggetto che trasporta elenco rimangono gli stessi nel codice sopra. Spero che l'approccio aiuti.

Problemi correlati