ToList () - crea un nuovo elenco?


176

Diciamo che ho una lezione

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

E ho un List<MyObject>, e lo faccio ToList()e poi cambio uno dei SimpleInt, il mio cambiamento verrà propagato 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 ...


Un modo per formularlo è quello di .ToList()fare una copia superficiale . I riferimenti vengono copiati, ma i nuovi riferimenti puntano ancora alle stesse istanze a cui fanno riferimento i riferimenti originali. Quando ci pensi, ToListnon puoi crearne uno new MyObject()quando MyObjectè un classtipo.
Jeppe Stig Nielsen,

Risposte:


217

Sì, ToListcreerà 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 SimpleIntproprietà di un oggetto a cui fa riferimento il nuovo elenco influirà anche sull'oggetto equivalente nell'elenco originale.

(Se è MyObjectstato dichiarato come structanziché come un classallora il nuovo elenco conterrebbe copie degli elementi nell'elenco originale e l'aggiornamento di una proprietà di un elemento nel nuovo elenco non influirebbe sull'elemento equivalente nell'elenco originale.)


1
Si noti inoltre che con una Listdelle strutture, un'assegnazione come objectList[0].SimpleInt=5non sarebbe consentita (errore di compilazione C #). Questo perché il valore restituito dall'accessorio dell'indicizzatore di elenco getnon è una variabile (è una copia restituita di un valore di struttura), pertanto l' impostazione del suo membro .SimpleIntcon un'espressione di assegnazione non è consentita (cambierebbe una copia che non viene conservata) . Bene, chi usa comunque le strutture mutabili?
Jeppe Stig Nielsen,

2
@Jeppe Stig Nielson: Sì. E, allo stesso modo, il compilatore ti impedirà anche di fare cose del genere foreach (var s in listOfStructs) { s.SimpleInt = 42; }. Il gotcha davvero brutto è quando provi qualcosa del genere listOfStructs.ForEach(s => s.SimpleInt = 42): il compilatore lo consente e il codice viene eseguito senza eccezioni, ma le strutture nell'elenco rimarranno invariate!
Luca,

62

Dalla fonte di Reflector:

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

Quindi sì, il tuo elenco originale non verrà aggiornato (ad esempio aggiunte o rimozioni), tuttavia gli oggetti referenziati lo faranno.


32

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

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

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


12

La risposta accettata risponde correttamente alla domanda del PO sulla base del suo esempio. Tuttavia, si applica solo quando ToListviene applicato a una raccolta di calcestruzzo; non vale quando gli elementi della sequenza sorgente devono ancora essere istanziati (a causa di un'esecuzione differita). In quest'ultimo caso, è possibile ottenere un nuovo set di elementi ogni volta che si chiama ToList(o si enumera la sequenza).

Ecco un adattamento del codice del PO per dimostrare 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 riportato può apparire forzato, questo comportamento può apparire come un bug sottile in altri scenari. Vedi il mio altro esempio per una situazione in cui fa sì che le attività vengano generate ripetutamente.


11

Sì, crea un nuovo elenco. Questo è di progettazione.

L'elenco conterrà gli stessi risultati della sequenza enumerabile originale, ma materializzati in una raccolta persistente (in memoria). Ciò consente di consumare i risultati più volte senza incorrere nel costo di ricalcolare la sequenza.

Il bello delle sequenze LINQ è che sono componibili. Spesso, ciò che IEnumerable<T>si ottiene è il risultato della combinazione di più operazioni di filtro, ordinamento e / o proiezione. Metodi di estensione come ToList()e ToArray()consentono di convertire la sequenza calcolata in una raccolta standard.


6

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


6

Mi sono imbattuto in questo vecchio post e ho pensato di aggiungere i miei due centesimi. In generale, in caso di dubbio, utilizzo rapidamente il metodo GetHashCode () su qualsiasi oggetto per verificare le identità. Quindi 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 rispondi sulla mia macchina -

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

Quindi essenzialmente l'oggetto che l'elenco contiene rimane lo stesso nel codice sopra. Spero che l'approccio aiuti.


4

Penso che questo equivale a chiedere se ToList fa una copia profonda o superficiale. Poiché ToList non ha modo di clonare MyObject, deve fare una copia superficiale, quindi l'elenco creato contiene gli stessi riferimenti di quello originale, quindi il codice restituisce 5.


2

ToList creerà un nuovo elenco.

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


2

Nel caso in cui l'oggetto di origine sia un vero IEnumerable (ovvero non solo una raccolta impacchettata come enumerabile), ToList () potrebbe NON restituire gli stessi riferimenti a oggetto dell'IEnumerable originale. Restituirà un nuovo Elenco di oggetti, ma tali oggetti potrebbero non essere uguali o addirittura uguali agli oggetti prodotti da IEnumerable quando vengono nuovamente elencati


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

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

Ora se aggiorni un elenco (aggiungendo o eliminando un elemento) che non si rifletterà nell'altro elenco.


1

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

La preoccupazione è che a volte potresti voler essere assolutamente sicuro che l'elenco restituito sia! = All'elenco originale. Poiché Microsoft non documenta che ToList restituirà un nuovo elenco, non possiamo esserne certi (a meno che qualcuno non abbia trovato tale documentazione). Potrebbe anche cambiare in futuro, anche se ora funziona.

nuovo elenco (IEnumerable enumerablestuff) è garantito per restituire un nuovo elenco. Vorrei usare questo invece.


2
Lo dice nella documentazione qui: " Il metodo ToList <TSource> (IEnumerable <TSource>) forza la valutazione immediata della query e restituisce un Elenco <T> che contiene i risultati della query. È possibile aggiungere questo metodo alla query per ottenere una copia cache dei risultati della query. "La seconda frase ribadisce che qualsiasi modifica all'elenco originale dopo la valutazione della query non ha alcun effetto sull'elenco restituito.
Raymond Chen,

1
@RaymondChen Questo vale per IEnumerables, ma non per gli elenchi. Sebbene ToListsembri creare un nuovo riferimento all'oggetto elenco quando viene chiamato su a List, BenB ha ragione quando afferma che ciò non è garantito dalla documentazione MS.
Teejay,

@RaymondChen Come per la fonte ChrisS, in IEnumerable<>.ToList()realtà è implementato come new List<>(source)e non esiste una sostituzione specifica per List<>, quindi List<>.ToList()restituisce effettivamente un nuovo riferimento all'oggetto elenco. Ma ancora una volta, secondo la documentazione di MS, non vi è alcuna garanzia che ciò non cambi in futuro, anche se probabilmente romperà la maggior parte del codice.
Teejay,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.