Lista przekazana przez ref - pomóż mi wyjaśnić to zachowanie

110

Spójrz na następujący program:

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;
    }
}

Zakładałem myList, że minie ref, a wynik minie

3
4

Lista jest rzeczywiście „przekazywana przez ref”, ale tylko sortfunkcja odnosi skutek. Poniższe stwierdzenie myList = myList2;nie ma żadnego skutku.

Tak więc wynik jest w rzeczywistości:

10
50
100

Czy możesz mi pomóc wyjaśnić to zachowanie? Jeśli rzeczywiście myListnie jest przekazywany-by-ref (jak wynika z myList = myList2braku efektu), jak działa myList.Sort()?

Zakładałem, że nawet to stwierdzenie nie odniesie skutku, a wynik będzie:

100
50
10
nmdr
źródło
Tylko obserwacja (i zdaję sobie sprawę, że problem został tutaj uproszczony), ale wydaje się, że najlepiej ChangeListbyłoby zwrócić List<int>raczej niż być a, voidjeśli w rzeczywistości jest to tworzenie nowej listy.
Jeff B

Odpowiedzi:

111

Jesteś przechodzącą odniesienie do wykazu , ale twój nie przechodzącą zmiennej listy przez odniesienie - tak podczas rozmowy ChangeListna wartość zmiennej (czyli odwołanie - myśleć „wskaźnik”) jest kopiowany - oraz zmiany do wartości danej parametr wewnątrz ChangeList nie jest widoczny TestMethod.

próbować:

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

To następnie przekazuje referencję do zmiennej lokalnej myRef (jak zadeklarowano w TestMethod); teraz, jeśli zmienisz przypisanie parametru wewnątrz ChangeList, ponownie przypisujesz zmienną wewnątrz TestMethod .

Marc Gravell
źródło
Rzeczywiście mogę to zrobić, ale chcę wiedzieć, jak działa sortowanie
nmdr
6
@Ngm - kiedy dzwonisz ChangeList, kopiowane jest tylko odniesienie - jest to ten sam obiekt. Jeśli zmienisz obiekt w jakiś sposób, wszystko , co ma odniesienie do tego obiektu, zobaczy zmianę.
Marc Gravell
225

Początkowo można to przedstawić graficznie w następujący sposób:

Stany Init

Następnie sortowanie jest stosowane myList.Sort(); Sortuj kolekcję

Wreszcie, kiedy to zrobiłeś: myList' = myList2straciłeś jedną z referencji, ale nie oryginał, a kolekcja została posortowana.

Zagubione odniesienie

Jeśli użyjesz przez referencję ( ref), wtedy myList'i myListstaną się takie same (tylko jedno odwołanie).

Uwaga: używam myList'do reprezentowania parametru, którego używasz ChangeList(ponieważ podałeś tę samą nazwę co oryginał)

Jaider
źródło
20

Oto łatwy sposób, aby to zrozumieć

  • Twoja lista to obiekt utworzony na stercie. Zmienna myListjest odniesieniem do tego obiektu.

  • W C # nigdy nie przekazujesz obiektów, przekazujesz ich odwołania według wartości.

  • Kiedy uzyskujesz dostęp do obiektu listy za pośrednictwem przekazanego odniesienia ChangeList(na przykład podczas sortowania), oryginalna lista jest zmieniana.

  • Przypisanie do ChangeListmetody jest dokonywane do wartości referencji, stąd żadne zmiany nie są dokonywane na oryginalnej liście (nadal na stercie, ale nie ma już odniesienia do zmiennej metody).

Unmesh Kondolikar
źródło
10

Ten link pomoże ci w zrozumieniu przekazywania przez odwołanie w C #. Zasadniczo, gdy obiekt typu referencyjnego jest przekazywany przez wartość do metody, tylko metody dostępne w tym obiekcie mogą modyfikować zawartość obiektu.

Na przykład metoda List.sort () zmienia zawartość listy, ale jeśli przypiszesz jakiś inny obiekt do tej samej zmiennej, przypisanie to będzie lokalne dla tej metody. Dlatego myList pozostaje niezmieniona.

Jeśli przekażemy obiekt typu referencyjnego za pomocą słowa kluczowego ref, to możemy przypisać inny obiekt do tej samej zmiennej, a to zmieni cały sam obiekt.

(Edit: to jest zaktualizowana wersja dokumentacji związanej powyżej).

Shekhar
źródło
5

C # po prostu wykonuje płytką kopię, gdy przechodzi przez wartość, chyba że dany obiekt jest wykonywany ICloneable(co najwyraźniej Listnie jest wykonywane przez klasę).

Oznacza to, że kopiuje Listsiebie, ale odniesienia do obiektów na liście pozostają takie same; to znaczy, wskaźniki nadal odnoszą się do tych samych obiektów, co oryginał List.

Jeśli zmienisz wartości rzeczy List, do których odnoszą się twoje nowe odniesienia, zmienisz Listrównież oryginał (ponieważ odnosi się on do tych samych obiektów). Jednak wtedy myListcałkowicie zmienisz odniesienia, na nowe List, a teraz tylko oryginał Listodwołuje się do tych liczb całkowitych.

Przeczytaj sekcję Przekazywanie parametrów typu odwołania w tym artykule MSDN na temat „Przekazywanie parametrów”, aby uzyskać więcej informacji.

„How do I Clone a Generic List in C #” from StackOverflow mówi o tym, jak utworzyć głęboką kopię listy.

Ethel Evans
źródło
3

Chociaż zgadzam się z tym, co wszyscy powiedzieli powyżej. Mam inne podejście do tego kodu. Zasadniczo przypisujesz nową listę do zmiennej lokalnej myList, a nie do zmiennej globalnej. jeśli zmienisz podpis ChangeList (List myList) na private void ChangeList () zobaczysz wynik 3, 4.

Oto moje rozumowanie ... Nawet jeśli lista jest przekazywana przez referencję, pomyśl o tym jako o przekazywaniu zmiennej wskaźnika według wartości. Kiedy wywołujesz ChangeList (myList), przekazujesz wskaźnik do (Global) myList. Teraz jest to przechowywane w (lokalnej) zmiennej myList. Więc teraz twoja (lokalna) mojaLista i (globalna) mojaLista wskazują na tę samą listę. Teraz wykonujesz sort => to działa, ponieważ (lokalna) myList odwołuje się do oryginalnej (globalnej) myList Następnie tworzysz nową listę i przypisujesz wskaźnik do tej (lokalnej) myList. Ale gdy tylko funkcja zakończy działanie, (lokalna) zmienna myList jest niszczona. 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;
    }
}
Sandeep
źródło
3

Użyj refsłowa kluczowego.

Spójrz na ostateczne odniesienie tutaj, aby zrozumieć przekazywanie parametrów.
Aby być konkretnym, spójrz na to , aby zrozumieć zachowanie kodu.

EDYCJA: Sortdziała na tym samym odwołaniu (przekazywanym przez wartość) i dlatego wartości są uporządkowane. Jednak przypisanie nowego wystąpienia do parametru nie zadziała, ponieważ parametr jest przekazywany przez wartość, chyba że wstawisz ref.

Umieszczanie refpozwala zmienić wskaźnik na odniesienie do nowego wystąpienia Listw twoim przypadku. Bez tego refmożesz pracować nad istniejącym parametrem, ale nie możesz wskazać na coś innego.

shahkalpesh
źródło
0

Istnieją dwie części pamięci przydzielonej dla obiektu typu referencyjnego. Jeden w stosie i jeden w stercie. Część na stosie (inaczej wskaźnik) zawiera odniesienie do części na stercie - gdzie przechowywane są rzeczywiste wartości.

Gdy słowo kluczowe ref nie jest używane, tworzona jest tylko kopia części na stosie i przekazywana do metody - referencja do tej samej części w stercie. Dlatego jeśli zmienisz coś w części sterty, te zmiany pozostaną. Jeśli zmienisz skopiowany wskaźnik - przypisując go tak, aby odnosił się do innego miejsca w stercie - nie wpłynie to na wskaźnik pochodzenia poza metodą.

Thinh Tran
źródło