Какая польза от «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"
user7116
источник
17
так что вы в основном получаете ссылку на оригинальную ссылку
lhahne
2
Вы можете изменить то, на что ссылается оригинальная ссылка, так что да.
user7116
1
Крис, твое объяснение великолепно; Спасибо за помощь в понимании этой концепции.
Андреас Греч
4
Таким образом, использование 'ref' для объекта похоже на использование двойных указателей в C ++?
Том Хейзел
1
@TomHazel: -ish , при условии, что вы используете «двойные» указатели в C ++, чтобы изменить то, на что указывает указатель.
user7116
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
Мне нравится аналогия передачи вашего собачьего поводка другу за то, что он передал справочную стоимость ... хотя она быстро ломается, потому что я думаю, что вы , вероятно, заметите, если ваш друг передаст вашу шицу доберману, прежде чем он вернет вас обратно поводок ;-)
corlettk
16

Очень хорошо объяснено здесь: http://msdn.microsoft.com/en-us/library/s6938f28.aspx

Аннотация из статьи:

Переменная ссылочного типа не содержит своих данных напрямую; он содержит ссылку на свои данные. Когда вы передаете параметр ссылочного типа по значению, можно изменить данные, на которые указывает ссылка, например значение члена класса. Однако вы не можете изменить значение самой ссылки; то есть вы не можете использовать одну и ту же ссылку для выделения памяти для нового класса и сохранения ее вне блока. Для этого передайте параметр, используя ключевое слово ref или out.

himanshupareek66
источник
4
Объяснение действительно очень приятно. Тем не менее, ответы только на ссылки не рекомендуется на SO. Я добавил резюме из статьи, для удобства читателей здесь.
Марсель
10

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

C # также имеет ключевое слово «out», которое очень похоже на ref, за исключением того, что с «ref» аргументы должны быть инициализированы перед вызовом метода, а с помощью «out» вы должны присвоить значение в получающем методе.

Rytmis
источник
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
}
springy76
источник
1

Параметр в методе, кажется, всегда передает копию, вопрос - копия чего. Копирование выполняется конструктором копирования для объекта, и поскольку все переменные являются объектами в C #, я считаю, что это относится ко всем из них. Переменные (объекты) похожи на людей, живущих по некоторым адресам. Мы либо меняем людей, живущих по этим адресам, либо можем создавать дополнительные ссылки на людей, живущих по этим адресам, в телефонной книге (делать мелкие копии). Таким образом, более одного идентификатора могут ссылаться на один и тот же адрес. Ссылочные типы требуют больше места, поэтому в отличие от типов значений, которые напрямую связаны стрелкой с их идентификатором в стеке, они имеют значение для другого адреса в куче (большее пространство для остановки). Это пространство нужно взять из кучи.

Тип значения: Идентификатор (содержит значение = адрес значения стека) ----> Значение типа значения

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

Единственный способ изменить значение - следовать стрелкам. Если одна стрелка теряется / изменяется таким образом, значение недоступно.

Петр Дрианов
источник
-1

Ссылочные переменные переносят адрес из одного места в другое, поэтому любое их обновление в любом месте отразится на всех местах, КОГДА-то использует REF. Справочная переменная (405) работает до тех пор, пока не будет выделена новая память для справочной переменной, переданной в методе.

После выделения новой памяти (410) изменение значения этого объекта (408) не будет отражаться повсеместно. За этим реф приходит. Ref - это ссылка на ссылку, поэтому всякий раз, когда выделяется новая память, он узнает, потому что он указывает на это местоположение, поэтому значение может быть доступно всем everyOne. Вы можете увидеть изображение для большей ясности.

Ссылка в ссылочной переменной

srbh
источник