Почему список передается без ссылки функции, которая действует как переданная с помощью ссылки?

Если я не ошибся, такое поведение для меня странно. Вместо того, чтобы объяснять, я опубликую ниже образец кода и расскажите, пожалуйста, почему я получаю вывод x, а не y.

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

Я предполагаю, что выход должен быть 3. Но я получаю результат как 5. Я понимаю, что выход может быть 5, если я сделаю это:

    private void button1_Click(object sender, EventArgs e)
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(ref l);
        MessageBox.Show(l.Count.ToString());
    }

    private void Fuss(ref List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

person nawfal    schedule 06.09.2011    source источник
comment
Вам, вероятно, также следует прочитать Передача параметров в C # Джона Скита.   -  person Randy supports Monica    schedule 06.09.2011


Ответы (7)


Это не действует так, как будто оно было передано исх.

void ChangeMe(List<int> list) {
  list = new List<int>();
  list.Add(10);
}
void ChangeMeReally(ref List<int> list) {
  list = new List<int>();
  list.Add(10);
}

Попытайся. Вы замечаете разницу?

Вы можете изменить содержимое списка (или любого ссылочного типа) только в том случае, если вы передадите его без ссылки (потому что, как говорили другие, вы передаете ссылку на объект в куче и, таким образом, изменяете ту же «память»).

Однако вы не можете изменить «список», «список» - это переменная, которая указывает на объект типа List. Вы можете изменить «список», только если передадите его по ссылке (чтобы он указывал где-то еще). Вы получаете копию ссылки, которую, если изменить, можно будет наблюдать только внутри вашего метода.

person chrisaut    schedule 06.09.2011

Параметры в C # передаются по значению, если они не отмечены модификаторами ref или out. Для ссылочных типов это означает, что ссылка передается по значению. Следовательно, в Fuss l относится к тому же экземпляру List<int>, что и его вызывающий. Следовательно, вызывающий абонент увидит любые изменения этого экземпляра List<int>.

Теперь, если вы пометите параметр l с помощью ref или out, то параметр будет передан по ссылке. Это означает, что в Fuss, l - это псевдоним места хранения, используемый в качестве параметра для вызова метода. Чтобы было ясно:

public void Fuss(ref List<int> l)

вызванный

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

Теперь, в Fuss, l - это псевдоним для list. В частности, если вы назначите новый экземпляр List<int> для l, вызывающий также увидит этот новый экземпляр, назначенный переменной list. В частности, если вы скажете

public void Fuss(ref List<int> l) {
    l = new List<int> { 1 };
}

тогда вызывающий теперь увидит список с одним элементом. Но если вы скажете

public void Fuss(List<int> l) {
    l = new List<int> { 1 };
}

и позвонить по

List<int> list = new List<int> { 1, 2, 3 };
Fuss(list);

тогда вызывающий по-прежнему будет видеть list как имеющий три элемента.

Прозрачный?

person jason    schedule 06.09.2011
comment
Я это понимаю. Вы говорите, что во втором случае (без ссылки), если мы попытаемся изменить l, это не отразится на вызывающем. Но как это отразится на моей проблеме, которую я поставил без ссылки? - person nawfal; 06.09.2011
comment
Я думаю, что ответ @Steven проясняет мне ваш ответ. То есть без ссылки я могу изменить содержимое списка, но не сам список! Кажется, с ref я могу делать и то, и другое. - person nawfal; 06.09.2011
comment
@nawfal: во втором случае и вызываемый, и вызывающий ссылаются на один и тот же экземпляр List<int>, поэтому, конечно, они оба видят изменения. В первом случае и вызываемый, и вызывающий используют одно и то же место хранения. Так что дело не только в том, что они оба видят одно и то же List<int>, но они оба могут изменить, на какой экземпляр List<int> смотрят друг друга! - person jason; 06.09.2011
comment
Я понял, о чем вы говорите. Большое спасибо. Но я получил более ясную мысль от FishBasketGordo и Стивена. Позвольте мне отметить их. Вместе вы трое разъяснили мне мысль. - person nawfal; 06.09.2011

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

private void Fuss(ref List<int> l)
{
    l = new List<int> { 4, 5 };
}

и вы увидите, что счетчик равен 2, потому что функция не только управляла исходным списком, но и самой ссылкой.

person Tomas Vana    schedule 06.09.2011

Переменная, параметр или поле типа «Список» или любого другого ссылочного типа на самом деле не содержит список (или объект любого другого класса). Вместо этого он будет содержать что-то вроде «Object ID # 29115» (конечно, не такую ​​фактическую строку, а комбинацию битов, которая по сути означает это). В другом месте система будет иметь индексированную коллекцию объектов, называемую кучей; если некоторая переменная типа List содержит «Object ID # 29115», то объект # 29115 в куче будет экземпляром List или производным от него типом.

Если MyFoo является переменной типа List, такой оператор, как 'MyFoo.Add ("George")', на самом деле не изменит MyFoo; вместо этого это означает: «Изучите идентификатор объекта, хранящийся в MyFoo, и вызовите метод« Добавить »для объекта, хранящегося в нем. Если MyFoo удерживал« Идентификатор объекта # 19533 »перед выполнением оператора, он продолжит делать это и после этого, но объект Для идентификатора № 19533 будет вызван метод Add (возможно, изменяющий этот объект). И наоборот, такой оператор, как «MyFoo = MyBar», заставит MyFoo содержать тот же идентификатор объекта, что и MyBar, но на самом деле ничего не будет делать с объектами в Если MyBar содержал «Object ID # 59212» перед оператором, то после оператора MyFoo также будет содержать «ObjectId # 59212». Ничего не произойдет ни с идентификатором объекта № 19533, ни с идентификатором объекта № 59212.

person supercat    schedule 16.11.2011

ByRef и ByVal применяются только к типам значений, а не к ссылочным типам, которые всегда передаются так, как если бы они были «byref».

Если вам нужно незаметно изменить список, используйте функцию «.ToList ()», и вы получите клон вашего списка.

Имейте в виду, что если ваш список содержит ссылочные типы, ваш «новый» список будет содержать указатели на те же объекты, что и исходный список.

person Wesley Long    schedule 06.09.2011
comment
чувак! этот ваш .ToList () спас мне день! - person Buda Florin; 28.06.2016
comment
Кроме того, вы также можете использовать метод .clone () - person Jamisco; 26.12.2017

Списки уже являются ссылочными типами, поэтому, когда вы передаете их методу, вы передаете ссылку. Любые Add вызовы повлияют на список в вызывающем абоненте.

Передача List<T> по ref ведет себя, по сути, как передача двойного указателя на этот список. Вот иллюстрация:

using System;
using System.Collections.Generic;

public class Test
{
    public static void Main()
    {
        List<int> l = new List<int>() { 1, 2, 3 };
        Fuss(l);
        Console.WriteLine(l.Count); // Count will now be 5.

        FussNonRef(l);
        Console.WriteLine(l.Count); // Count will still be 5 because we 
                                    // overwrote the copy of our reference 
                                    // in FussNonRef.

        FussRef(ref l);
        Console.WriteLine(l.Count); // Count will be 2 because we changed
                                    // our reference in FussRef.
    }

    private static void Fuss(List<int> l)
    {
        l.Add(4);
        l.Add(5);
    }

    private static void FussNonRef(List<int> l)
    {
        l = new List<int>();
        l.Add(6);
        l.Add(7);
    }

    private static void FussRef(ref List<int> l)
    {
        l = new List<int>();
        l.Add(8);
        l.Add(9);
    }
}
person FishBasketGordo    schedule 06.09.2011

По значению передаются только примитивные типы, такие как int, double и т. Д.

Сложные типы (например, список) передаются по ссылке (которая является указателем передачи по значению, если быть точным).

person deha    schedule 06.09.2011
comment
Это не правильно. Все параметры передаются по значению, если они не отмечены ref или out. - person jason; 06.09.2011
comment
Вы сказали, что только примитивные типы ... передаются по значению. Сложные типы ... передаются по ссылке. Как я уже сказал, все параметры передаются по значению, если они не отмечены ref или out, а не только примитивные типы. Сложный - это не вполне определенный термин; Я предполагаю, что вы имеете в виду ссылочные типы. Типы параметров как ссылочные типы передаются по значению, если они не отмечены ref или out. - person jason; 06.09.2011
comment
Нет, я имел в виду, что метод копирует не объект, а только его адрес. Подумайте об этом, как в C ++, где у вас нет ключевого слова ref. Прочтите еще раз внимательно. Я согласен с вашей точкой зрения о сложном, ссылка была бы лучшим словом. - person deha; 06.09.2011
comment
C # - это не C ++. Нет, я имел в виду, что метод копирует не объект, а только его адрес. Если отбросить небрежность (метод не копирует объект), значение копируется перед передачей методу. Всегда, всегда, всегда (если не отмечено ref или out). Для типов значений экземпляр - это значение. Для ссылочных типов значение является ссылкой. Обратите внимание, что это не то же самое, что референт! - person jason; 06.09.2011
comment
Вы написали в скобках то же, что и я. Так что я снова не понимаю, в чем дело. Добавление ref означает передачу указателя на указатель, но все же по значению - так что, по вашему мнению, это ... передача по значению. - person deha; 06.09.2011
comment
Что случилось? Вы сказали, что по значению передаются только примитивные типы. Это неправда. Все параметры передаются по значению, если параметр не отмечен ref или out. Вы сказали, что сложные типы передаются по ссылке. Это неверно (опять же, если предположить, что под сложным вы подразумеваете ссылку). Все параметры передаются по значению, если параметр не отмечен ref или out. - person jason; 06.09.2011
comment
Я до сих пор не понимаю, почему ты считаешь плохой только мой ответ. - person deha; 06.09.2011
comment
предоставить указатель = передать по значению, предоставить указатель на указатель - передать по ссылке. Это твоя логика. Для меня немного странно. В программировании предоставление указателя / адреса называется передачей по ссылке (потому что мы не копируем объект только его адрес) - я имел в виду это. И я не вижу причин, по которым мой ответ был понижен, а другие нет. Конец темы для меня здесь. - person deha; 06.09.2011
comment
Вы не понимаете, что означает передача по ссылке. Передача по ссылке не означает передачу ссылки на объект. Это означает, что вызываемый, как вызывающий, видит ТАКОЕ МЕСТО ХРАНЕНИЯ. - person jason; 06.09.2011