Яке використання “ref” для змінних типу еталону в C #?


176

Я розумію, що якщо я передаю тип значення ( int, structтощо) як параметр (без refключового слова), копія цієї змінної передається методу, але якщо я використовую refключове слово, передається посилання на цю змінну, не новий.

Але з типами посилань, як-от класи, навіть без refключового слова, посилання передається на метод, а не на копію. Тож у чому полягає використання refключового слова для типів посилань?


Візьмемо для прикладу:

var x = new Foo();

Яка різниця між наступними?

void Bar(Foo y) {
    y.Name = "2";
}

і

void Bar(ref Foo y) {
    y.Name = "2";
}

Відповіді:


154

Ви можете змінити, що fooвказує на використання y:

Foo foo = new Foo("1");

void Bar(ref Foo y)
{
    y = new Foo("2");
}

Bar(ref foo);
// foo.Name == "2"

17
тож ви в основному отримуєте посилання на оригінальну посилання
lhahne

2
Ви можете змінити те, на що посилається початкова посилання, так що так.
user7116

1
Крісе, твоє пояснення чудово; Дякуємо, що допомогли мені зрозуміти цю концепцію.
Андреас Греч

4
Тож використання 'ref' на об’єкті - це як використання подвійних покажчиків у C ++?
Том Хейзел

1
@TomHazel: -ish , якщо ви використовуєте "подвійні" покажчики в C ++, щоб змінити, на що вказує вказівник.
користувач7116

29

Є випадки, коли ви хочете змінити фактичну посилання, а не об’єкт, на який вказували:

void Swap<T>(ref T x, ref T y) {
    T t = x;
    x = y;
    y = t;
}

var test = new[] { "0", "1" };
Swap(ref test[0], ref test[1]);

21

Джон Скіт написав чудову статтю про проходження параметрів у C #. У ньому чітко деталізовано точну поведінку та використання параметрів, що передаються, за значенням, за посиланням ( ref) та за результатами ( out).

Ось важлива цитата на цій сторінці стосовно refпараметрів:

Довідкові параметри не передають значення змінних, що використовуються у виклику члена функції - вони використовують самі змінні. Замість того, щоб створити нове місце зберігання для змінної в декларації члена функції, використовується одне і те ж місце зберігання, тому значення змінної у функціональному члені та значення опорного параметра завжди будуть однаковими. Довідкові параметри потребують модифікатора ref як частини як декларації, так і виклику - це означає, що це завжди зрозуміло, коли ви передаєте щось за посиланням.


11
Мені подобається аналогія передачі вашої собаки повідці другові для передачі посилальної вартості ... Хоча, я думаю, ви , мабуть, помітите, якби ваш друг торгував вашим ситцу доберману, перш ніж він передав вам назад
повідок

16

Дуже красиво пояснено тут: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Реферат із статті:

Змінна еталонного типу безпосередньо не містить своїх даних; він містить посилання на свої дані. Коли ви передаєте параметр типу опорного значення за значенням, можна змінити дані, на які вказує посилання, наприклад значення члена класу. Однак ви не можете змінити значення самої посилання; тобто ви не можете використовувати одне і те ж посилання для розподілу пам'яті для нового класу і зберігати його поза блоком. Для цього передайте параметр за допомогою ключового слова ref або out.


4
Пояснення дійсно дуже приємне. Однак відповіді, що стосуються лише посилань, відмовляють від SO. Я додав резюме зі статті, як зручність для читачів тут.
Марсель

10

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

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


5

Це дозволяє змінювати передану посилання, наприклад

void Bar()
{
    var y = new Foo();
    Baz(ref y);
}

void Baz(ref Foo y)
{
    y.Name = "2";

    // Overwrite the reference
    y = new Foo();
}

Ви також можете скористатися , якщо вам не байдуже посилання, передане в:

void Bar()
{
    var y = new Foo();
    Baz(out y);
}

void Baz(out Foo y)
{
    // Return a new reference
    y = new Foo();
}

4

Ще одна купа коду

class O
{
    public int prop = 0;
}

class Program
{
    static void Main(string[] args)
    {
        O o1 = new O();
        o1.prop = 1;

        O o2 = new O();
        o2.prop = 2;

        o1modifier(o1);
        o2modifier(ref o2);

        Console.WriteLine("1 : " + o1.prop.ToString());
        Console.WriteLine("2 : " + o2.prop.ToString());
        Console.ReadLine();
    }

    static void o1modifier(O o)
    {
        o = new O();
        o.prop = 3;
    }

    static void o2modifier(ref O o)
    {
        o = new O();
        o.prop = 4;
    }
}

3

Окрім існуючих відповідей:

Як ви запитували про різницю двох методів: Немає співвідношення (ntra) при використанні refабо out:

class Foo { }
class FooBar : Foo { }

static void Bar(Foo foo) { }
static void Bar(ref Foo foo) { foo = new Foo(); }

void Main()
{
    Foo foo = null;
    Bar(foo);           // OK
    Bar(ref foo);       // OK

    FooBar fooBar = null;
    Bar(fooBar);        // OK (covariance)
    Bar(ref fooBar);    // compile time error
}

1

Параметр у методі, здається, завжди передає копію, питання - це копія того, що. Копія робиться конструктором копій для об'єкта, і оскільки всі змінні є "Об'єктом в C #", я вважаю, що це стосується всіх них. Змінні (об'єкти) схожі на людей, що живуть за деякими адресами. Ми або змінюємо людей, які живуть за цими адресами, або можемо створювати більше посилань на людей, які живуть за цими адресами в телефонній книзі (робимо дрібні копії). Отже, більше однієї ідентифікатора може посилатися на одну і ту ж адресу. Типи посилань хочуть більше простору, тому на відміну від типів значень, які безпосередньо пов'язані стрілкою до їх ідентифікатора в стеку, вони мають значення для іншої адреси в купі (більший простір для проживання). Цей простір потрібно взяти з купи.

Тип значення: Індикатор (містить значення = адреса значення стека) ----> Значення типу значення

Тип довідки: Ідентифікатор (містить значення = адреса значення стека) ----> (містить значення = адреса величини купи) ----> Значення купи (найчастіше містить адреси до інших значень), уявіть, що більше стрілок стикаються в різних вказівки до масиву [0], масиву [1], масиву [2]

Єдиний спосіб змінити значення - це слідувати стрілками. Якщо одна стрілка загубиться / зміниться у спосіб, недоступний для цього значення.


-1

Довідкові змінні переносять адресу з одного місця в інше, тому будь-яке оновлення на них у будь-якому місці відображатиметься на всіх місцях ТАКОМ, для чого використовується REF. Довідкова змінна (405) хороша до тих пір, поки новій пам'яті не буде виділено опорну змінну, передану методом.

Після виділення нової пам'яті (410) зміна значення цього об'єкта (408) відображатиметься не скрізь. За цим приходить реф. Ref - це довідкова література, тому щоразу, коли нова пам'ять буде виділена, познайомтеся, тому що вона вказує на це місце, тому значення може ділитися кожним. Ви можете побачити зображення для більшої чіткості.

Посилання на змінну

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