Elenco passato da ref - aiutami a spiegare questo comportamento


109

Dai un'occhiata al seguente programma:

class Test
{
    List<int> myList = new List<int>();

    public void TestMethod()
    {
        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList(myList);

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList(List<int> myList)
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

Ho pensato che myListsarebbe passato refe l'output sarebbe passato

3
4

L'elenco è effettivamente "passato da ref", ma solo la sortfunzione ha effetto. La seguente dichiarazione myList = myList2;non ha effetto.

Quindi l'output è in effetti:

10
50
100

Puoi aiutarmi a spiegare questo comportamento? Se effettivamente myListnon viene passato per riferimento (come sembra myList = myList2non avere effetto), come ha myList.Sort()effetto?

Stavo assumendo che anche quella dichiarazione non avesse effetto e che l'output fosse:

100
50
10

Solo un'osservazione (e mi rendo conto che il problema è stato semplificato qui), ma sembra che sarebbe meglio ChangeListrestituire a List<int>piuttosto che essere a voidse in realtà sta creando un nuovo elenco.
Jeff B

Risposte:


110

Stai passando un riferimento alla lista , ma non stai passando la variabile della lista per riferimento - quindi quando chiami ChangeListil valore della variabile (cioè il riferimento - pensa "puntatore") viene copiato - e cambia al valore della parametro all'interno ChangeList non sono visti da TestMethod.

provare:

private void ChangeList(ref List<int> myList) {...}
...
ChangeList(ref myList);

Questo quindi passa un riferimento alla variabile locale myRef (come dichiarato in TestMethod); ora, se riassegni il parametro all'interno, ChangeListstai riassegnando anche la variabile all'interno TestMethod .


In effetti posso farlo, ma voglio sapere come funziona l'ordinamento
nmdr

6
@Ngm - quando chiami ChangeList, viene copiato solo il riferimento - è lo stesso oggetto. Se modifichi l'oggetto in qualche modo, tutto ciò che ha un riferimento a quell'oggetto vedrà il cambiamento.
Marc Gravell

225

Inizialmente, può essere rappresentato graficamente come segue:

Init afferma

Quindi, viene applicato l'ordinamento myList.Sort(); Ordina raccolta

Alla fine, quando hai fatto:, myList' = myList2hai perso quello del riferimento ma non l'originale e la raccolta è rimasta ordinata.

Riferimento perso

Se usi by reference ( ref) allora myList'e myListdiventerà lo stesso (solo un riferimento).

Nota: io uso myList'per rappresentare il parametro che usi in ChangeList(perché hai dato lo stesso nome dell'originale)


20

Ecco un modo semplice per capirlo

  • La tua lista è un oggetto creato su heap. La variabile myListè un riferimento a quell'oggetto.

  • In C # non si passano mai oggetti, si passano i loro riferimenti per valore.

  • Quando si accede all'oggetto elenco tramite il riferimento passato ChangeList(durante l'ordinamento, ad esempio), l'elenco originale viene modificato.

  • L'assegnazione al ChangeListmetodo viene effettuata al valore del riferimento, quindi non vengono apportate modifiche all'elenco originale (ancora nell'heap ma non più referenziato sulla variabile del metodo).


10

Questo collegamento ti aiuterà a comprendere il passaggio per riferimento in C #. Fondamentalmente, quando un oggetto di tipo riferimento viene passato per valore a un metodo, solo i metodi disponibili su quell'oggetto possono modificare il contenuto dell'oggetto.

Ad esempio, il metodo List.sort () cambia il contenuto dell'elenco ma se si assegna un altro oggetto alla stessa variabile, tale assegnazione è locale per quel metodo. Ecco perché myList rimane invariato.

Se passiamo un oggetto di tipo riferimento utilizzando la parola chiave ref, possiamo assegnare un altro oggetto alla stessa variabile e questo cambia l'intero oggetto stesso.

(Modifica: questa è la versione aggiornata della documentazione collegata sopra.)


5

C # fa solo una copia superficiale quando passa per valore a meno che l'oggetto in questione non venga eseguito ICloneable(cosa che apparentemente la Listclasse non fa).

Ciò significa che copia Listse stesso, ma i riferimenti agli oggetti all'interno della lista rimangono gli stessi; ovvero, i puntatori continuano a fare riferimento agli stessi oggetti dell'originale List.

Se modifichi i valori delle cose che i tuoi nuovi Listriferimenti, cambiano anche l'originale List(dato che fa riferimento agli stessi oggetti). Tuttavia, si cambia quindi myListcompletamente ciò che fa riferimento a un nuovo Liste ora solo l'originale Listfa riferimento a quei numeri interi.

Per ulteriori informazioni, leggere la sezione relativa al passaggio di parametri di tipo di riferimento da questo articolo di MSDN su "Passaggio di parametri" .

"Come faccio a clonare un elenco generico in C #" da StackOverflow parla di come creare una copia completa di un elenco.


3

Mentre sono d'accordo con quanto tutti hanno detto sopra. Ho una visione diversa di questo codice. Fondamentalmente stai assegnando il nuovo elenco alla variabile locale myList non a quella globale. se cambi la firma di ChangeList (List myList) in private void ChangeList () vedrai l'output di 3, 4.

Ecco il mio ragionamento ... Anche se list viene passato per riferimento, pensalo come passare una variabile puntatore per valore Quando chiami ChangeList (myList) stai passando il puntatore a (Global) myList. Ora questo è memorizzato nella variabile (locale) myList. Quindi ora la tua myList (locale) e myList (globale) puntano alla stessa lista. Ora fai un sort => funziona perché (local) myList fa riferimento alla myList originale (globale) Successivamente crei una nuova lista e assegni il puntatore a quella myList (locale). Ma non appena la funzione esce, la variabile myList (locale) viene distrutta. HTH

class Test
{
    List<int> myList = new List<int>();
    public void TestMethod()
    {

        myList.Add(100);
        myList.Add(50);
        myList.Add(10);

        ChangeList();

        foreach (int i in myList)
        {
            Console.WriteLine(i);
        }
    }

    private void ChangeList()
    {
        myList.Sort();

        List<int> myList2 = new List<int>();
        myList2.Add(3);
        myList2.Add(4);

        myList = myList2;
    }
}

2

Usa la refparola chiave.

Guarda il riferimento definitivo qui per capire i parametri di passaggio.
Per essere precisi, guarda questo , per capire il comportamento del codice.

EDIT: Sortfunziona sullo stesso riferimento (che viene passato per valore) e quindi i valori sono ordinati. Tuttavia, l'assegnazione di una nuova istanza al parametro non funzionerà perché il parametro viene passato per valore, a meno che non si inserisca ref.

Mettere refti consente di cambiare il puntatore al riferimento a una nuova istanza di Listnel tuo caso. Senza ref, puoi lavorare sul parametro esistente, ma non puoi farlo puntare a qualcos'altro.


0

Esistono due parti di memoria allocate per un oggetto di tipo riferimento. Uno in pila e uno in pila. La parte nello stack (ovvero un puntatore) contiene un riferimento alla parte nell'heap, dove sono memorizzati i valori effettivi.

Quando la parola chiave ref non viene utilizzata, viene creata solo una copia della parte nello stack e passata al metodo - riferimento alla stessa parte nell'heap. Pertanto, se modifichi qualcosa nella parte heap, quei cambiamenti rimarranno. Se si modifica il puntatore copiato, assegnandolo per fare riferimento a un altro punto nell'heap, non influirà sul puntatore di origine al di fuori del metodo.

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.