Список, переданий посилання - допоможіть мені пояснити цю поведінку


109

Погляньте на наступну програму:

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

Я припускав myList, що пройшов би мимо ref, а вихід буде

3
4

Список справді "передається посилання", але sortдіє лише функція. Наступне твердження myList = myList2;не має ефекту.

Отже, результат насправді:

10
50
100

Чи можете ви допомогти мені пояснити таку поведінку? Якщо насправді myListне проходить перелік змін (як видно з myList = myList2набрання чинності), як це myList.Sort()набуває чинності?

Я припускав, що навіть це твердження не набуває чинності, а результат:

100
50
10

Просто спостереження (і я розумію, що проблема тут спрощена), але, схоже, найкраще ChangeListповернутись, List<int>а не бути, voidякщо це насправді створює новий список.
Джефф Б

Відповіді:


110

Ви передаєте посилання на список , але ваш НЕ передаючи список змінних за посиланням - так що, коли ви телефонуєте ChangeListв значення змінної (тобто посилання - думаю , «покажчик») копіюється - і зміни в значенням Параметр всередині ChangeList не бачив TestMethod.

спробуйте:

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

Потім передається посилання на локальну змінну myRef (як заявлено в TestMethod); Тепер, якщо ви перепризначили параметр всередині, ChangeListви також перепризначите змінну всередині TestMethod .


Дійсно, я можу це зробити, але я хочу знати, як сортування діє
nmdr

6
@Ngm - коли ви телефонуєте ChangeList, копіюється лише посилання - це той самий об’єкт. Якщо ви змінили об'єкт якимось чином, все, що має посилання на цей об’єкт, побачить зміни.
Марк Гравелл

225

Спочатку його можна представити графічно так:

Ініт штатів

Потім застосовується сортування myList.Sort(); Сортувати колекцію

Нарешті, коли ви зробили: myList' = myList2ви втратили один із довідкових, але не оригінал, і колекція залишилася відсортованою.

Втрачена довідка

Якщо ви користуєтесь посиланням ( ref), то myList'і myListстане однаковим (лише одна посилання).

Примітка: я використовую myList'для представлення параметра, який ви використовуєте ChangeList(тому що ви дали те саме ім'я, що і оригінал)


20

Ось простий спосіб зрозуміти це

  • Ваш Список - це об'єкт, створений у купі. Змінна myListє посиланням на цей об'єкт.

  • У C # ви ніколи не передаєте об'єкти, ви передаєте їх посилання за значенням.

  • Коли ви отримуєте доступ до об'єкта списку за допомогою переданої посилання в ChangeList(наприклад, сортування, наприклад), початковий список змінюється.

  • Присвоєння ChangeListметоду робиться значенню посилання, отже, ніяких змін у вихідному списку не проводиться (все ще в купі, але вже не посилається на змінну методу).


10

Це посилання допоможе вам зрозуміти перехід посилання на C #. В основному, коли об'єкт опорного типу передається за значенням методу, тільки методи, доступні на цьому об'єкті, можуть змінювати вміст об'єкта.

Наприклад, метод List.sort () змінює вміст списку, але якщо ви присвоюєте якомусь іншому об'єкту ту саму змінну, це призначення є локальним для цього методу. Ось чому мій список залишається незмінним.

Якщо ми передамо об'єкт опорного типу за допомогою ключового слова ref, тоді ми можемо призначити якийсь інший об'єкт цій же змінній, що змінює сам об'єкт.

(Редагувати: це оновлена ​​версія документації, пов'язаної вище.)


5

C # просто робить неглибоку копію, коли вона передається за значенням, якщо не буде виконаний відповідний об'єкт ICloneable(що, очевидно, Listклас не робить)

Це означає, що вона копіює Listсебе, але посилання на об'єкти всередині списку залишаються однаковими; тобто вказівники продовжують посилатись на ті самі об’єкти, що і на оригінал List.

Якщо ви змінюєте значення речей у своїх нових Listпосиланнях, ви також змінюєте оригінал List(оскільки він посилається на ті самі об'єкти). Однак ви потім myListповністю змінюєте посилання на нове List, а тепер лише Listцілі числа посилаються на оригінал .

Прочитайте розділ « Параметри передачі посилань» із цієї статті MSDN на тему «Параметри передачі» для отримання додаткової інформації.

"Як клонувати загальний список у C #" з StackOverflow, розповідає про те, як зробити глибоку копію списку.


3

Поки я згоден з тим, що всі сказали вище. Я по-різному сприймаю цей код. В основному ви присвоюєте новий список локальній змінній myList, а не глобальному. якщо ви зміните підпис ChangeList (Список мого списку) на приватний недійсний ChangeList (), ви побачите вихід 3, 4.

Ось моє міркування ... Навіть незважаючи на те, що список передається за посиланням, подумайте про це як передачу змінної вказівника за значенням Коли ви викликаєте ChangeList (myList), ви передаєте вказівник на (Global) myList. Тепер це зберігається у (локальній) змінній myList. Тож тепер ваш (локальний) мій список та (глобальний) мій список вказують на той самий список. Тепер ви робите sort => це працює, тому що (локальний) myList посилається на оригінальний (глобальний) myList. Далі ви створюєте новий список і призначаєте вказівник цьому (мій) мій список. Але як тільки функція закриває змінну (локального) myList знищується. 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

Використовуйте refключове слово.

Подивіться на остаточну посилання тут , щоб зрозуміти передачі параметрів.
Щоб бути конкретним, подивіться на це , щоб зрозуміти поведінку коду.

EDIT: Sortпрацює над тією ж посиланням (що передається за значенням) і, отже, впорядковуються значення. Однак призначення нового екземпляра параметру не буде працювати, оскільки параметр передається за значенням, якщо ви не ставите ref.

Якщо розмістити, refви можете змінити вказівник на посилання на новий екземпляр Listу вашому випадку. Без цього refви можете працювати над існуючим параметром, але не можете вказувати на щось інше.


0

Для об'єкта опорного типу виділено дві частини пам'яті. Один у штабелі та один у купі. Частина в стеку (він же вказівник) містить посилання на частину в купі - де зберігаються фактичні значення.

Якщо ключове слово ref не використовується, просто створюється копія частини в стеку і передається методу - посилання на ту саму частину в купі. Тому якщо ви щось поміните в купі, ці зміни залишаться. Якщо ви зміните скопійований покажчик - призначивши його для посилання на інше місце в купі - це не вплине на вихідний вказівник поза методом.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.