Зачем использовать ключевое слово ref при передаче объекта? - PullRequest
254 голосов
/ 09 октября 2008

Если я передаю объект методу, зачем мне использовать ключевое слово 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» означает, что объект был передан как ссылка.

Ответы [ 11 ]

265 голосов
/ 09 октября 2008

Передайте 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, но относится к совершенно другому объекту.

Это также может быть полезно, если вы хотите изменить значение неизменяемого объекта, например, 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;
}
81 голосов
/ 09 октября 2008

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

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

43 голосов
/ 09 октября 2008

В .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; }
        }
    }
17 голосов
/ 09 октября 2008

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

15 голосов
/ 09 октября 2008

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

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

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

7 голосов
/ 17 июля 2015

Думайте о переменных (например, 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 голосов
/ 09 октября 2008

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

3 голосов
/ 10 мая 2015

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

  • Caller
  • вызываемая сторона.
3 голосов
/ 09 октября 2008

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

1 голос
/ 09 октября 2008

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

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

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

...