Когда использовать ref vs out

Кто-то спросил меня на днях, когда они должны использовать ключевое слово параметра вместо ref . Хотя я (я думаю) понимаю разницу между ключевыми словами ref и out (которые были заданы раньше ), и лучшим объяснением кажется, что ref == in и out , какие-то (гипотетические или кодовые) примеры, где я всегда должен использовать out и не ref .

Поскольку ref является более общим, почему вы когда-либо хотите использовать out ? Это просто синтаксический сахар?

Вы должны использовать out если вам не нужна ref .

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

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

В качестве незначительной разницы параметр out не должен инициализироваться.

Пример для out :

 string a, b; person.GetBothNames(out a, out b); 

где GetBothNames – метод для извлечения двух значений атомарно, метод не будет изменять поведение, какое бы ни было a и b. Если вызов идет на сервер на Гавайях, копирование начальных значений отсюда на Гавайи – это трата пропускной способности. Аналогичный fragment с использованием ref:

 string a = String.Empty, b = String.Empty; person.GetBothNames(ref a, ref b); 

может смутить читателей, потому что похоже, что начальные значения a и b являются релевантными (хотя имя метода указывает, что это не так).

Пример для ref :

 string name = textbox.Text; bool didModify = validator.SuggestValidName(ref name); 

Здесь исходное значение имеет отношение к методу.

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

Кроме того, ref и out относятся не только к типам значений. Они также позволяют сбросить объект, на который ссылается ссылочный тип из метода.

Вы правы в том, что семантически ref предоставляет функциональность «in» и «out», тогда как только предоставляет «выход». Есть несколько вещей, которые следует учитывать:

  1. out требует, чтобы метод, принимающий параметр MUST, в какой-то момент перед возвратом, присваивал значение переменной. Вы находите этот шаблон в некоторых classах хранения данных ключа / значения, таких как Dictionary , где у вас есть функции, такие как TryGetValue . Эта функция принимает параметр out который содержит значение, которое будет получено при извлечении. Для вызывающего не было бы смысла передавать значение этой функции, поэтому используется для гарантии того, что какое-то значение будет в переменной после вызова, даже если это не «реальные» данные (в случае TryGetValue где ключ отсутствует).
  2. out и ref по-разному сортируются при взаимодействии с кодом взаимодействия

Кроме того, важно отметить, что, хотя ссылочные типы и типы значений различаются по характеру их значения, каждая переменная в вашем приложении указывает на местоположение памяти, которое имеет значение даже для ссылочных типов. Просто случается, что со ссылочными типами значение, содержащееся в этом месте памяти, является другой ячейкой памяти. Когда вы передаете значения функции (или выполняете любое другое назначение переменной), значение этой переменной копируется в другую переменную. Для типов значений это означает, что копируется весь контент типа. Для ссылочных типов это означает, что место памяти скопировано. В любом случае, он создает копию данных, содержащихся в переменной. Единственное реальное релевантность, которое это имеет дело с семантикой назначения; при назначении переменной или передаче по значению (по умолчанию), когда новое присвоение производится исходной (или новой) переменной, оно не влияет на другую переменную. В случае ссылочных типов да, изменения, внесенные в экземпляр , доступны с обеих сторон, но это потому, что фактическая переменная является всего лишь указателем на другую ячейку памяти; содержимое переменной – место памяти – на самом деле не изменилось.

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

Это зависит от контекста компиляции (см. Пример ниже).

out и ref оба означают передачу переменной по ссылке, но ref требует, чтобы переменная была инициализирована перед передачей, что может быть важным отличием в контексте Marshaling (Interop: UmanagedToManagedTransition или наоборот)

MSDN предупреждает :

Do not confuse the concept of passing by reference with the concept of reference types. The two concepts are not the same. A method parameter can be modified by ref regardless of whether it is a value type or a reference type. There is no boxing of a value type when it is passed by reference.

Из официальных документов MSDN:

  • :

The out keyword causes arguments to be passed by reference. This is similar to the ref keyword, except that ref requires that the variable be initialized before being passed

  • ref :

The ref keyword causes an argument to be passed by reference, not by value. The effect of passing by reference is that any change to the parameter in the method is reflected in the underlying argument variable in the calling method. The value of a reference parameter is always the same as the value of the underlying argument variable.

Мы можем проверить, что out и ref действительно одинаковы при назначении аргумента:

CIL Пример :

Рассмотрим следующий пример

 static class outRefTest{ public static int myfunc(int x){x=0; return x; } public static void myfuncOut(out int x){x=0;} public static void myfuncRef(ref int x){x=0;} public static void myfuncRefEmpty(ref int x){} // Define other methods and classes here } 

в CIL инструкции myfuncOut и myfuncRef идентичны, как ожидалось.

 outRefTest.myfunc: IL_0000: nop IL_0001: ldc.i4.0 IL_0002: starg.s 00 IL_0004: ldarg.0 IL_0005: stloc.0 IL_0006: br.s IL_0008 IL_0008: ldloc.0 IL_0009: ret outRefTest.myfuncOut: IL_0000: nop IL_0001: ldarg.0 IL_0002: ldc.i4.0 IL_0003: stind.i4 IL_0004: ret outRefTest.myfuncRef: IL_0000: nop IL_0001: ldarg.0 IL_0002: ldc.i4.0 IL_0003: stind.i4 IL_0004: ret outRefTest.myfuncRefEmpty: IL_0000: nop IL_0001: ret 

nop : no operation, ldloc : загрузить local, stloc : stack local, ldarg : load argument, bs.s : branch to target ….

(См. Список инструкций CIL )

Ниже приведены некоторые примечания, которые я вытащил из этой статьи кодекса на C # Out Vs Ref

  1. Он должен использоваться только тогда, когда мы ожидаем несколько выходов из функции или метода. Мысль о структурах также может быть хорошим вариантом для того же.
  2. REF и OUT – это ключевые слова, которые определяют, как данные передаются от вызывающего абонента до вызываемого абонента и наоборот.
  3. В REF-данные проходят два пути. От звонящего до вызываемого и наоборот.
  4. In Out передает только один путь от вызываемого абонента. В этом случае, если Caller попытался отправить данные вызываемому, это будет упущено / отклонено.

Если вы визуальный человек, то посмотрите это видео на YouTube, которое демонстрирует разницу практически https://www.youtube.com/watch?v=lYdcY5zulXA

Ниже изображения отображаются различия визуально

C # Out Vs Ref

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

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

 int x; Foo(ref x); // error: x is uninitialized void Bar(out int x) {} // error: x was not written to 

Например, int.TryParse возвращает bool и принимает параметр out int :

 int value; if (int.TryParse(numericString, out value)) { /* numericString was parsed into value, now do stuff */ } else { /* numericString couldn't be parsed */ } 

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

Для ref , вы можете посмотреть Interlocked.Increment :

 int x = 4; Interlocked.Increment(ref x); 

Interlocked.Increment атомарно увеличивает значение x . Поскольку вам нужно прочитать x чтобы увеличить его, это ситуация, когда ref более подходит. Вы полностью заботитесь о том, что было до того, как оно было передано в Increment .

В следующей версии C # будет возможно объявить переменные в параметрах out , добавив еще больший упор на их выходную природу:

 if (int.TryParse(numericString, out int value)) { // 'value' exists and was declared in the `if` statement } else { // conversion didn't work, 'value' doesn't exist here } 

out является более ограниченной версией ref .

В теле метода вам необходимо назначить все параметры перед выходом из метода. Также игнорируются значения, назначенные параметру out , тогда как ref требует их назначения.

Таким образом out вы можете:

 int a, b, c = foo(out a, out b); 

где ref требует присвоения a и b.

Как это звучит:

out = только инициализировать / заполнять параметр (параметр должен быть пустым) вернуть его равным

ref = reference, стандартный параметр (возможно, со значением), но функция может модифицировать его.

Просто пояснить комментарий OP, что использование ref и out является «ссылкой на тип значения или структуру, объявленные вне метода», который уже был установлен неверно.

Рассмотрим использование ref на StringBuilder, который является ссылочным типом:

 private void Nullify(StringBuilder sb, string message) { sb.Append(message); sb = null; } // -- snip -- StringBuilder sb = new StringBuilder(); string message = "Hi Guy"; Nullify(sb, message); System.Console.WriteLine(sb.ToString()); // Output // Hi Guy 

В связи с этим:

 private void Nullify(ref StringBuilder sb, string message) { sb.Append(message); sb = null; } // -- snip -- StringBuilder sb = new StringBuilder(); string message = "Hi Guy"; Nullify(ref sb, message); System.Console.WriteLine(sb.ToString()); // Output // NullReferenceException 

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

Ключевое слово out приводит к передаче аргументов по ссылке. Это похоже на ключевое слово ref , за исключением того, что ref требует, чтобы переменная была инициализирована до ее передачи. Чтобы использовать параметр out , и определение метода, и метод вызова должны явно использовать ключевое слово out . Например: C #

 class OutExample { static void Method(out int i) { i = 44; } static void Main() { int value; Method(out value); // value is now 44 } } 

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

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

 class CS0663_Example { // Compiler error CS0663: "Cannot define overloaded // methods that differ only on ref and out". public void SampleMethod(out int i) { } public void SampleMethod(ref int i) { } } 

Однако перегрузка может быть выполнена, если один метод принимает аргумент ref или out а другой не использует ни того, ни другого: C #

 class OutOverloadExample { public void SampleMethod(int i) { } public void SampleMethod(out int i) { i = 5; } } 

Свойства не являются переменными и поэтому не могут быть переданы как out параметры.

Сведения о передаче массивов см. В разделе «Передача массивов с использованием ref и out (Руководство по программированию на C #).

Вы не можете использовать ключевые слова ref и out для следующих методов:

 Async methods, which you define by using the async modifier. Iterator methods, which include a yield return or yield break statement. 

пример

Объявление метода out полезно, если вы хотите, чтобы метод возвращал несколько значений. В следующем примере используется для возврата трех переменных с помощью одного вызова метода. Обратите внимание, что третьему аргументу присваивается значение null. Это позволяет методам возвращать значения необязательно. C #

 class OutReturnExample { static void Method(out int i, out string s1, out string s2) { i = 44; s1 = "I've been returned"; s2 = null; } static void Main() { int value; string str1, str2; Method(out value, out str1, out str2); // value is now 44 // str1 is now "I've been returned" // str2 is (still) null; } } 

Аргумент, переданный как ref, должен быть инициализирован перед передачей методу, тогда как параметр out не должен быть инициализирован перед передачей методу.

почему вы когда-нибудь хотите использовать?

Чтобы другие знали, что переменная будет инициализирована, когда она вернется из вызываемого метода!

Как упоминалось выше: «для параметра out метод вызова должен назначать значение перед возвратом метода ».

пример:

 Car car; SetUpCar(out car); car.drive(); // You know car is initialized. 

В основном, как ref и out для передачи объекта / значения между методами

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

out : аргумент не инициализирован и должен быть инициализирован в методе

ref : Аргумент уже инициализирован и может быть прочитан и обновлен в методе.

Что такое «ref» для ссылочных типов?

Вы можете изменить данную ссылку на другой экземпляр.

Вы знали?

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

  2. Вы не можете использовать ключевые слова ref и out для следующих методов:

    • Асинхронные методы, которые вы определяете с помощью модификатора async.
    • Методы Итератора, которые include в себя инструкцию возврата доходности или выхода.
  3. Свойства не являются переменными и поэтому не могут быть переданы как выходные параметры.

Дополнительные примечания относительно C # 7:
В C # 7 нет необходимости предубеждать переменные, используя out. Итак, такой код:

 public void PrintCoordinates(Point p) { int x, y; // have to "predeclare" p.GetCoordinates(out x, out y); WriteLine($"({x}, {y})"); } 

Можно написать так:

 public void PrintCoordinates(Point p) { p.GetCoordinates(out int x, out int y); WriteLine($"({x}, {y})"); } 

Источник: что нового в C # 7.

  • Как перегрузить оператор ++ двумя разными способами для postfix a ++ и prefix ++ a?
  • Как я могу расширить вызов базовых classов вариационных шаблонов?
  • Страница vs Окно в WPF?
  • Сколько памяти использует объект C # / .NET?
  • Обнаружение режима полета на iOS
  • ARC запрещает объекты Objective-C в структурах или объединениях, несмотря на маркировку файла -fno-objc-arc
  • Параметры шаблона шаблона
  • использование оператора FileStream и / или StreamReader - Предупреждения Visual Studio 2012
  • OpenGL определяет положение вершин в пикселях
  • Сколько байтов без знака длинное?
  • Где я могу найти стандартные документы C ++ 11?
  • Interesting Posts

    Word, заменяющий раздел «следующая страница», прерывается с непрерывными разрывами раздела

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

    Могу ли я отключить печатные листы небольших целых чисел в виде строк в оболочке Erlang?

    React Native android build не удалось. Местоположение SDK не найдено

    Что такое Android PendingIntent?

    Как стилизовать поле подтверждения по умолчанию с помощью только css?

    Миллионы 3D-точек: как найти 10 из них ближе всего к данной точке?

    Время сеанса терминала ssh, как это можно изменить

    Упреждающий базовый аут с HttpUrlConnection?

    Regex разделите строку, но сохраните разделители

    Получить общий тип java.util.List

    Как перенести hdd установки Windows на новый компьютер?

    Как предотвратить изменение яркости экрана ноутбука при отключении / включении питания аккумулятора

    Как восстановить ключ продукта Windows Vista?

    Создайте гиперссылку из URL и заголовка в Excel

    Давайте будем гением компьютера.