Список передан по ссылке - помогите мне объяснить это поведение

110

Взгляните на следующую программу:

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если он фактически создает новый список.
Jeff B

Ответы:

111

Вы передаете ссылку на список , но ваш не передавая список переменных по ссылке - так что, когда вы звоните 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вы потеряли одну из ссылок, но не оригинал, и коллекция осталась отсортированной.

Утерянная ссылка

Если вы используете by reference ( ref), тогда myList'и myListстанет таким же (только одна ссылка).

Примечание: я использую myList'для представления параметра, который вы используете ChangeList(потому что вы дали то же имя, что и оригинал)

Джайдер
источник
20

Вот простой способ понять это

  • Ваш список - это объект, созданный в куче. Переменная myList- это ссылка на этот объект.

  • В C # вы никогда не передаете объекты, вы передаете их ссылки по значению.

  • Когда вы обращаетесь к объекту списка через переданную ссылку ChangeList(например, при сортировке), исходный список изменяется.

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

Унмеш Кондоликар
источник
10

Эта ссылка поможет вам понять переход по ссылке в C #. По сути, когда объект ссылочного типа передается по значению методу, только методы, доступные для этого объекта, могут изменять содержимое объекта.

Например, метод List.sort () изменяет содержимое списка, но если вы назначаете какой-либо другой объект той же переменной, это назначение является локальным для этого метода. Поэтому myList остается неизменным.

Если мы передаем объект ссылочного типа с помощью ключевого слова ref, тогда мы можем назначить какой-либо другой объект той же переменной, и это изменит сам объект.

(Изменить: это обновленная версия документации по ссылке выше.)

Шехар
источник
5

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

Это означает, что он копирует Listсебя, но ссылки на объекты внутри списка остаются прежними; то есть указатели продолжают ссылаться на те же объекты, что и оригинал List.

Если вы измените значения вещей, которые вы используете в новых Listссылках, вы также измените исходный List(поскольку он ссылается на те же объекты). Однако затем вы myListполностью меняете ссылки на новые List, и теперь только оригинал Listссылается на эти целые числа.

Прочтите раздел «Передача параметров ссылочного типа » этой статьи MSDN «Передача параметров» для получения дополнительной информации.

«Как клонировать общий список на C #» из StackOverflow рассказывает о том, как сделать полную копию списка.

Этель Эванс
источник
3

Хотя я согласен с тем, что все сказали выше. У меня другой взгляд на этот код. В основном вы назначаете новый список локальной переменной myList, а не глобальной. если вы измените подпись ChangeList (List myList) на private void ChangeList (), вы увидите результат 3, 4.

Вот мои рассуждения ... Даже если список передается по ссылке, думайте об этом как о передаче переменной-указателя по значению. Когда вы вызываете ChangeList (myList), вы передаете указатель на (Global) myList. Теперь это хранится в (локальной) переменной myList. Итак, теперь ваш (локальный) myList и (глобальный) myList указывают на один и тот же список. Теперь вы выполняете sort => это работает, потому что (локальный) myList ссылается на исходный (глобальный) 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;
    }
}
Sandeep
источник
3

Используйте refключевое слово.

Посмотрите полную ссылку здесь, чтобы понять передаваемые параметры.
Чтобы быть конкретным, посмотрите на это , чтобы понять поведение кода.

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

Помещение refпозволяет вам изменить указатель на ссылку на новый экземпляр Listв вашем случае. Без него refвы можете работать с существующим параметром, но не можете заставить его указывать на что-то другое.

Шахкалпеш
источник
0

Для объекта ссылочного типа выделены две части памяти. Один в стеке и один в куче. Часть в стеке (также известная как указатель) содержит ссылку на часть в куче, где хранятся фактические значения.

Когда ключевое слово ref не используется, создается только копия части в стеке и передается методу - ссылка на ту же часть в куче. Поэтому, если вы что-то измените в части кучи, эти изменения останутся. Если вы измените скопированный указатель - назначив его для ссылки на другое место в куче - это не повлияет на указатель источника вне метода.

Thinh Tran
источник