Когда использовать ref, а когда нет необходимости в C # - PullRequest
98 голосов
/ 11 марта 2009

У меня есть объект, который находится в состоянии памяти программы, а также есть некоторые другие рабочие функции, которым я передаю объект для изменения состояния. Я передавал его по ссылкам на рабочие функции. Однако я наткнулся на следующую функцию.

byte[] received_s = new byte[2048];
IPEndPoint tmpIpEndPoint = new IPEndPoint(IPAddress.Any, UdpPort_msg);
EndPoint remoteEP = (tmpIpEndPoint);

int sz = soUdp_msg.ReceiveFrom(received_s, ref remoteEP); 

Меня смущает, потому что и received_s, и remoteEP возвращают вещи из функции. Почему remoteEP нужен ref, а received_s нет?

Я также программист на c, поэтому у меня проблемы с вылетом указателей из головы.

Edit: Похоже, что объекты в C # являются указателями на объект под капотом. Поэтому, когда вы передаете объект в функцию, вы можете затем изменить содержимое объекта через указатель, и единственная вещь, переданная функции, - это указатель на объект, чтобы сам объект не копировался. Вы используете ref или out, если хотите иметь возможность отключить или создать новый объект в функции, который похож на двойной указатель.

Ответы [ 8 ]

198 голосов
/ 11 марта 2009

Краткий ответ: прочитайте мою статью о передаче аргументов .

Длинный ответ: когда параметр ссылочного типа передается по значению, передается только ссылка, не копия объекта. Это похоже на передачу указателя (по значению) в C или C ++. Изменения в значении самого параметра не будут видны вызывающей стороне, но изменения в объекте, на который указывает ссылка на , будут видны .

Когда параметр (любого типа) передается по ссылке , это означает, что вызывающая сторона видит любые изменения в параметре - изменения в параметре являются изменениями в переменная.

Статья объясняет все это более подробно, конечно:)

Полезный ответ: Вам почти никогда не нужно использовать ref / out . Это в основном способ получения другого возвращаемого значения, и его обычно следует избегать именно потому, что это означает, что метод, вероятно, пытается сделать слишком много. Это не всегда так (TryParse и т. Д. Являются каноническими примерами разумного использования out), но использование ref / out должно быть относительной редкостью.

26 голосов
/ 11 марта 2009

Думайте о параметре non-ref как о указателе, а о параметре ref как о двойном указателе. Это помогло мне больше всего.

Вы почти никогда не должны передавать значения по реф. Я подозреваю, что если бы не проблемы взаимодействия, команда .Net никогда бы не включила его в первоначальную спецификацию. ОО способ решения большинства проблем, которые решают параметры ref, заключается в следующем:

Для нескольких возвращаемых значений

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

Для примитивов, которые изменяются в методе в результате вызова метода (у метода есть побочные эффекты на параметры примитива)

  • Реализуйте метод в объекте как метод экземпляра и управляйте состоянием объекта (не параметрами) как часть вызова метода
  • Используйте решение с несколькими возвращаемыми значениями и объедините возвращаемые значения с вашим состоянием
  • Создайте объект, содержащий состояние, которым можно управлять методом, и передайте этот объект в качестве параметра, а не сами примитивы.
9 голосов
/ 11 марта 2009

Вы могли бы написать целое приложение на C # и никогда не передавать какие-либо объекты / структуры по ссылке.

У меня был профессор, который сказал мне это:

Единственное место, где вы бы использовали ссылки, это то, где вы либо:

  1. Хотите передать большой объект (т. Е. Объекты / структура имеет объекты / структуры внутри него на несколько уровней), и копирование будет быть дорогим и
  2. Вы вызываете Framework, Windows API или другой API, который требует это.

Не делай этого только потому, что можешь. Вы можете получить немного в задницу некоторыми неприятные ошибки, если вы начинаете изменять значения в параметре и не обращая внимание.

Я согласен с его советом, и за пять с лишним лет, прошедших после школы, он мне никогда не понадобился, кроме как вызывать Framework или Windows API.

3 голосов
/ 11 марта 2009

Считайте, что ссылка означает, что вы передаете указатель по ссылке. Отсутствие ссылки означает, что вы передаете указатель по значению.

Еще лучше, игнорируйте то, что я только что сказал (это, вероятно, вводит в заблуждение, особенно с типами значений), и прочитайте Эта страница MSDN .

3 голосов
/ 11 марта 2009

Так как receive_s - это массив, вы передаете указатель на этот массив. Функция манипулирует существующими данными на месте, не изменяя базовое местоположение или указатель. Ключевое слово ref означает, что вы передаете фактический указатель на местоположение и обновляете этот указатель во внешней функции, поэтому значение во внешней функции изменится.

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

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

0 голосов
/ 25 апреля 2018

Хотя я в целом согласен с ответом Джона Скита и некоторыми другими ответами, есть вариант использования ref, который предназначен для ужесточения оптимизации производительности. Во время профилирования производительности было замечено, что установка возвращаемого значения метода имеет незначительное влияние на производительность, тогда как использование ref в качестве аргумента, при котором возвращаемое значение вводится в этот параметр, приводит к удалению этого небольшого узкого места.

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

0 голосов
/ 14 апреля 2014

Сначала правило нулевого поля, примитивы передаются по значению (стек) и не примитив по ссылке (куча) в контексте задействованных типов.

Параметры по умолчанию передаются по значению. Хороший пост, который объясняет вещи в деталях. http://yoda.arachsys.com/csharp/parameters.html

Student myStudent = new Student {Name="A",RollNo=1};

ChangeName(myStudent);

static void ChangeName(Student s1)
{
  s1.Name = "Z"; // myStudent.Name will also change from A to Z
                // {AS s1 and myStudent both refers to same Heap(Memory)
                //Student being the non-Primitive type
}

ChangeNameVersion2(ref myStudent);
static void ChangeNameVersion2(ref Student s1)
{
  s1.Name = "Z"; // Not any difference {same as **ChangeName**}
}

static void ChangeNameVersion3(ref Student s1)
{
    s1 = new Student{Name="Champ"};

    // reference(myStudent) will also point toward this new Object having new memory
    // previous mystudent memory will be released as it is not pointed by any object
}

Можно сказать (с предупреждением) Непримитивные типы - это не что иное, как Указатели И когда мы передаем их по ссылке, мы можем сказать, что мы передаем Двойной указатель

0 голосов
/ 23 февраля 2012

Насколько я понимаю, все объекты, полученные из класса Object, передаются как указатели, тогда как обычные типы (int, struct) не передаются как указатели и требуют ref. Я не уверен насчет строки (в конечном итоге она получена из класса Object?)

...