Зачем использовать ключевое слово ref при передаче объекта?


291

Если я передаю объект методу, зачем мне использовать ключевое слово ref? Разве это не поведение по умолчанию в любом случае?

Например:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

Вывод «Bar» означает, что объект был передан как ссылка.

Ответы:


299

Передайте, refесли вы хотите изменить объект:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

После вызова DoSomething tне ссылается на оригинал new TestRef, но ссылается на совершенно другой объект.

Это также может быть полезно, если вы хотите изменить значение неизменяемого объекта, например, a string. Вы не можете изменить значение, stringесли оно было создано. Но, используя ref, вы можете создать функцию, которая изменяет строку на другую, которая имеет другое значение.

Изменить: как уже упоминали другие люди. Это не очень хорошая идея, refесли она не нужна. Использование refдает методу свободу изменять аргумент для чего-то другого, вызывающие методы должны быть закодированы, чтобы гарантировать, что они обрабатывают эту возможность.

Кроме того, когда тип параметра является объектом, переменные объекта всегда действуют как ссылки на объект. Это означает, что при использовании refключевого слова вы получаете ссылку на ссылку. Это позволяет вам делать вещи, как описано в приведенном выше примере. Но когда тип параметра является примитивным значением (например,int ), тогда, если этот параметр назначен внутри метода, значение переданного аргумента будет изменено после возврата метода:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

88

Необходимо различать «передачу ссылки по значению» и «передачу параметра / аргумента по ссылке».

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


1
Ну, я столкнулся с проблемой при обновлении VB6 в код .Net C #. Существуют сигнатуры функций / методов, которые принимают ref, out и обычные параметры. Итак, как мы можем лучше различить разницу между простым параметром и ссылкой?
BonCodigo

2
@bonCodigo: Не уверен, что вы подразумеваете под «лучше различать» - это часть подписи, и вы также должны указать refна сайте вызовов ... где еще вы хотели бы, чтобы его различали? Семантика также достаточно ясна, но должна быть выражена осторожно (а не «объекты передаются по ссылке», что является обычным упрощением).
Джон Скит

я не знаю, почему визуальная студия до сих пор не показывает явно то, что передано
MonsterMMORPG

3
@MonsterMMORPG: Я не знаю, что ты имеешь в виду под этим, я боюсь.
Джон Скит

57

В .NET, когда вы передаете какой-либо параметр методу, создается копия. В типах значений означает, что любое изменение, внесенное вами в значение, находится в области действия метода и теряется при выходе из метода.

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

Как уже говорили люди, назначение является модификацией ссылки, поэтому теряется:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

Методы выше не изменяют исходный объект.

Небольшая модификация вашего примера

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

6
Мне нравится этот ответ лучше, чем принятый ответ. Это объясняет более четко, что происходит при передаче переменной ссылочного типа с ключевым словом ref. Спасибо!
Стефан

17

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


16

С помощью refвы можете написать:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

И т будет изменен после завершения метода.


8

Думайте о переменных (например foo) ссылочных типов (например List<T>) как об удерживающих идентификаторах объекта формы "Объект # 24601". Предположим, оператор foo = new List<int> {1,5,7,9};причины fooдержать «Объект # 24601» (список с четырьмя пунктами). Затем вызывающий foo.Lengthзапросит у Объекта # 24601 его длину, и он ответит 4, поэтому foo.Lengthбудет равен 4.

Если fooметод передается без использования ref, этот метод может внести изменения в объект № 24601. Как следствие таких изменений, они foo.Lengthмогут больше не равняться 4. Однако сам метод не сможет измениться foo, что будет продолжать содержать «Объект № 24601».

Передача fooв качестве refпараметра позволит вызываемому методу вносить изменения не только в объект № 24601, но и в fooсамого себя. Метод может создать новый объект # 8675309 и сохранить ссылку на него в foo. Если это так, fooон больше не будет содержать «Объект № 24601», а вместо «Объект № 8675309».

На практике переменные ссылочного типа не содержат строки вида «Объект # 8675309»; они даже не содержат ничего, что могло бы значимо преобразоваться в число. Даже если каждая переменная ссылочного типа будет содержать некоторую битовую комбинацию, не существует фиксированной связи между битовыми комбинациями, хранящимися в таких переменных, и объектами, которые они идентифицируют. Нет никакого способа, которым код мог бы извлечь информацию из объекта или ссылку на него, а затем определить, идентифицировала ли другая ссылка тот же объект, если только код не содержал или знал ссылку, идентифицирующую исходный объект.


5

Это похоже на передачу указателя на указатель в C. В .NET это позволит вам изменить то, на что ссылается оригинальный T, лично, хотя я думаю, что если вы делаете это в .NET, у вас, вероятно, есть проблема с дизайном!


3

Используя refключевое слово со ссылочными типами, вы фактически передаете ссылку на ссылку. Во многих отношениях это то же самое, что и использование outключевого слова, но с небольшим отличием в том, что нет никакой гарантии, что метод фактически назначит что-либо параметру ref'ed.


3

ref подражает (или ведет себя) как глобальная область только для двух областей:

  • гость
  • Вызываемый абонент.

1

Однако если вы передаете значение, все иначе. Вы можете принудительно передать значение по ссылке. Это позволяет вам передавать целое число, например, в метод, и заставлять метод изменять целое число от вашего имени.


4
Независимо от того, передаете ли вы ссылку или значение типа значения, поведение по умолчанию - передача по значению. Вам просто нужно понять, что с ссылочными типами передаваемое вами значение является ссылкой. Это не то же самое, что передача по ссылке.
Джон Скит

1

Ref обозначает, может ли функция взять в руки сам объект или только его значение.

Передача по ссылке не связана с языком; это стратегия привязки параметров рядом с передачей по значению, передачей по имени, передачей по необходимости и т. д ...

Заметка: имя класса TestRef- ужасно плохой выбор в этом контексте;).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.