Когда использовать ref, а когда в C # нет необходимости


104

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

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 # являются указателями на объект под капотом. Поэтому, когда вы передаете объект функции, вы можете затем изменять содержимое объекта с помощью указателя, и единственное, что передается в функцию, - это указатель на объект, поэтому сам объект не копируется. Вы используете ref или out, если хотите иметь возможность выключить или создать новый объект в функции, который похож на двойной указатель.

Ответы:


217

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

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

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

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

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


38
Я думаю, вы перепутали свой короткий и длинный ответ; это большая статья!
Outlaw Programmer

23
@Outlaw: Да, но сам короткий ответ, директива о прочтении статьи, состоит всего из 6 слов :)
Джон Скит,

27
Ссылка! :)
gonzobrains

5
@Liam Использование ref, как вы, может показаться вам более явным, но на самом деле это может сбить с толку других программистов (тех, кто все равно знает, что это ключевое слово делает), потому что вы, по сути, говорите потенциальным вызывающим абонентам: «Я могу изменить переменную, которую вы используется в вызывающем методе, то есть переназначить его другому объекту (или даже, если возможно, null), чтобы не цепляться за него или убедиться, что вы подтвердили его, когда я закончу с ним ». Это довольно сильно и полностью отличается от «этот объект может быть изменен», что всегда имеет место, когда вы передаете ссылку на объект в качестве параметра.
mbargiel

1
@ M.Mimpen: C # 7 (надеюсь) позволитif (int.TryParse(text, out int value)) { ... use value here ... }
Джон Скит,

26

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

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

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

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

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

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

8
Боже. Мне пришлось бы прочитать это 20 раз, чтобы понять это. Для меня это похоже на кучу лишней работы, чтобы сделать что-то простое.
PositiveGuy

Платформа .NET не следует вашему правилу №1. ('Для нескольких возвращаемых значений создайте структуры'). Взять, к примеру IPAddress.TryParse(string, out IPAddress).
Swen Kooij 06

@SwenKooij Ты прав. Я предполагаю, что в большинстве мест, где они используют параметры out, либо (а) они обертывают Win32 API, либо (б) это было раннее время, и программисты на C ++ принимали решения.
Майкл Медоуз

@SwenKooij Я знаю, что ответ пришел с опозданием на много лет, но это так. Мы привыкли к TryParse, но это не значит, что это хорошо. Было бы лучше, если бы if (int.TryParse("123", out var theInt) { /* use theInt */ }у нас было var candidate = int.TrialParse("123"); if (candidate.Parsed) { /* do something with candidate.Value */ }больше кода, но он намного больше соответствует дизайну языка C #.
Майкл Медоуз

9

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

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

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

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

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

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


3
Если вы планируете реализовать «Своп», передача по ссылке может оказаться полезной.
Брайан,

@Chris Будет ли проблема, если я использую ключевое слово ref для небольших объектов?
ManirajSS

@TechnikEmpire «Но изменения объекта в рамках вызываемой функции не отражаются обратно в вызывающую сторону». Это не правильно. Если я передам Person в SetName(person, "John Doe"), свойство name изменится, и это изменение будет отражено для вызывающей стороны.
M. Mimpen

@ M.Mimpen Комментарий удален. На тот момент я едва освоил C # и явно говорил о своих * $$. Спасибо, что обратили на это мое внимание.

@Chris - Я почти уверен, что передать "большой" объект не дорого. Если вы просто передаете его по значению, вы все равно просто передаете единственный указатель, верно?
Ян

3

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

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

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


3

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

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


На самом деле неправда. По крайней мере, вторая часть. Любой ссылочный тип всегда будет передаваться по ссылке, независимо от того, используете ли вы ссылку или нет.
Эрик Функенбуш,

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

2
Тип ссылки без ссылки не передается по ссылке. Ссылка на ссылочный тип передается по значению. Но если вы хотите думать о ссылке как о указателе на что-то, на что ссылаются, то, что я сказал, имеет смысл (но такое представление может вводить в заблуждение). Отсюда и мое предупреждение.
Брайан,

Ссылка мертва - попробуйте вместо этого - docs.microsoft.com/en-us/dotnet/csharp/language-reference/…
GIVE-ME-

0

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


Это может потребовать некоторого пояснения. В чем разница между ценными и эталонными номерами? Ответ, почему это актуально для использования ключевого слова ref в параметре?
oɔɯǝɹ

В .net все, кроме указателей, параметров типов и интерфейсов, является производным от Object. Важно понимать, что «обычные» типы (правильно называемые «типами значений») тоже наследуются от объекта. Вы правы с этого момента: типы значений (по умолчанию) передаются по значению, тогда как ссылочные типы передаются по ссылке. Если вы хотите изменить тип значения, а не возвращать новый (обрабатывать его как ссылочный тип внутри метода), вам придется использовать для него ключевое слово ref. Но это плохой стиль, и вы просто не должны этого делать, если не уверены, что должны :)
buddybubble

1
чтобы ответить на ваш вопрос: строка является производным от объекта. Это ссылочный тип, который ведет себя как тип значения (по соображениям производительности и логическим причинам)
buddybubble

0

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

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


-1

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

Участвующие параметры по умолчанию передаются по значению. Хороший пост, в котором все подробно объясняется. 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
}

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


По умолчанию в C # все параметры передаются по значению. Любой параметр можно передать по ссылке. Для ссылочных типов передаваемое значение (либо по ссылке, либо по значению) само является ссылкой. Это совершенно не зависит от того, как он передается.
Servy

Согласен @Servy! «Когда мы слышим слова« ссылка »или« значение », мы должны четко понимать, имели ли мы в виду, что параметр является параметром ссылки или значения, или мы имеем в виду, что задействованный тип является ссылочным или типом значения. Путаница на моей стороне, КОГДА я сначала сказал правило нулевого значения, примитивы передаются по значению (стек), а непримитивные по ссылке (куча), я имел в виду задействованные ТИПЫ, а не параметр! Когда мы говорим о параметрах, вы правы , Все параметры передаются по значению, по умолчанию в C #.
Сурендер Сингх Малик,

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

1
Вы передаете параметры по ссылке или по значению. Термины «по ссылке» и «по значению» не используются для описания того, является ли тип типом значения или типом ссылки.
Servy

1
Нет, дело не в восприятии. Когда вы используете неправильный термин для обозначения чего-либо, это утверждение становится неверным . Для правильных утверждений важно использовать правильную терминологию для обозначения понятий.
Servy
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.