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

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

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

10 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Когда вы передаете тип значения в качестве аргумента методу (например, метод с именем «ChangeAge()») с ключевым словом ref, тогда изменения, внесенные в тип значения в методе «ChangeAge()», также будут отражены обратно в метод. значение в вызывающем методе.

Итак, основная цель ключевого слова «ref» — изменить значение типа значения, которое передается в качестве аргумента методу. По умолчанию C# передает аргументы как значения.

      class PassByReferenceExample

{
    static void Main(string[] args)
     
    {
      
       int age = 12 ;
       Console.WriteLine(age) ;
       
       ChangeName(ref age);

       //printing the name after changing the value by passing the 
       //reference
       
       //Now the Name will be changed from "Alex" to "Adam"

       Console.WriteLine(age);

     }

     static void ChangeAge(ref int age)

     {
        age = 20 ;

     }

  }
 

Итак, в приведенном выше примере, когда тип значения (int) «возраст» передается в качестве аргумента методу «ChangeAge()» с ключевым словом «ref», изменения, внесенные в «возраст» в методе «ChangeAge()», также будет отражено обратно в вызывающем методе.

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

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

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

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

Старайтесь изо всех сил избегать использования ref/out, это против написания чистого кода и кода плохого качества.

Передача типов по ссылке (используя out или ref) требует опыта работы с указателями, понимания различий между типами значений и ссылочными типами и обработки методов, которые имеют несколько возвращаемых значений. Кроме того, не все понимают разницу между параметрами out и ref.

пожалуйста, проверьте

https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca1045

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

Другие вопросы по тегам