2008-10-09 3 views
204

Если я передаю объект методу, почему я должен использовать ключевое слово ref? Разве это не поведение по умолчанию?Зачем использовать ключевое слово '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; } 
} 

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

ответ

209

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

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

+4

Независимо от того, передаете ли вы ссылку или значение типа значения, поведение по умолчанию должно проходить по значению. Вам просто нужно понять, что с ссылочными типами значение, которое вы передаете *, является * ссылкой. Это не то же самое, что передавать * по * ссылке. – 2008-10-09 12:04:05

13

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

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

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

16

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

3

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

3

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

1

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

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

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

69

Вам нужно провести различие между «передачей ссылки по значению» и «передачей параметра/аргумента по ссылке».

Я написал reasonably long article on the subject, чтобы избежать необходимости тщательно писать каждый раз, когда это приходит на телеконференции :)

+1

Ну, я столкнулся с проблемой при обновлении VB6 в .Net C#. Существуют сигнатуры функций/методов, которые принимают параметры ref, out и plain. Итак, как мы можем лучше отличить разницу между простым параметром и ссылкой? – bonCodigo 2015-03-18 10:20:12

+1

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

21

В .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; } 
     } 
    } 
3

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

  • Caller
  • вызываемого абонента.
6

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

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

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

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