Почему list, когда передается без ref в функцию, действующую как переданный с ref? - PullRequest
14 голосов
/ 06 сентября 2011

Если я не понял это ужасно неправильно, это поведение странно для меня. Вместо объяснения я опубликую пример кода ниже и, пожалуйста, скажите мне, почему я получаю вывод 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);
    }

Ответы [ 7 ]

14 голосов
/ 06 сентября 2011

Он не действует, как переданный по ссылке.

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

Попробуй. Вы заметили разницу?

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

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

6 голосов
/ 06 сентября 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 как имеющий три элемента.

Очистить?

3 голосов
/ 06 сентября 2011

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

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

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

2 голосов
/ 16 ноября 2011

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

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

2 голосов
/ 06 сентября 2011

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

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

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

2 голосов
/ 06 сентября 2011

Списки уже являются ссылочными типами, поэтому, когда вы передаете их методу, вы передаете ссылку.Любые 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);
    }
}
0 голосов
/ 06 сентября 2011

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

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

...