Когда использовать в отношении refs vs out

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

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

Ответ 1

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

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

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

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

Пример для out:

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

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

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);

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

Ответ 2

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

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

Ответ 3

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

  • out требует, чтобы метод, принимающий параметр MUST, в какой-то момент перед возвратом присваивал значение переменной. Этот шаблон можно найти в некоторых классах хранения данных ключа/значения, таких как Dictionary<K,V>, где у вас есть такие функции, как TryGetValue. Эта функция принимает параметр out, который сохраняет значение, которое будет получено при извлечении. Для вызывающего не было бы смысла передавать значение в эту функцию, поэтому out используется, чтобы гарантировать, что какое-то значение будет в переменной после вызова, даже если это не "реальные" данные (в case TryGetValue, где ключ отсутствует).
  • out и ref параметры сортируются по-разному при работе с кодом взаимодействия

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

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

Ответ 4

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

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

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: load local, stloc: stack local, ldarg: load argument, bs.s: branch to target....

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

Ответ 5

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

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

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

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

С# Out Vs Ref

Ответ 6

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

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

int x;
Foo(ref x); // error: x is uninitialized

void Bar(out int x) {}  // error: x was not written to

Например, int.TryParse возвращает a 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 решили выбрать out здесь, так как им все равно, что int мог быть раньше.

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

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

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

В следующей версии С# будет возможно объявить переменную в параметрах 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
}

Ответ 7

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

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

Итак out позволяет:

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

где ref потребуется назначить a и b.

Ответ 8

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

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

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

Ответ 9

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

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

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. Следующий код, например, не будет компилироваться: С#

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, а другой не использует ни того, ни другого: С#

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

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

Информацию о передаче массивов см. в разделе Передача массивов с помощью ref и out (Руководство по программированию на С#).

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

Async methods, which you define by using the async modifier.

Iterator methods, which include a yield return or yield break statement.

Пример

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

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

Ответ 10

Просто пояснить комментарий 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

Ответ 11

Как использовать in или out или ref в С#?

  • Все ключевые слова в C# имеют одинаковую функциональность, но с некоторыми границами.
  • in аргументы не могут быть изменены вызванным методом.
  • ref аргументы могут быть изменены.
  • ref должен быть инициализирован перед использованием вызывающей стороной, его можно прочитать и обновить в методе.
  • out аргументы должны быть изменены вызывающей стороной.
  • out аргументы должны быть инициализированы в методе
  • Переменные, переданные в качестве аргументов in, должны быть инициализированы перед передачей в вызове метода. Однако вызываемый метод не может присвоить значение или изменить аргумент.

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

  • Асинхронные методы, которые вы определяете с помощью модификатора async.
  • Методы итератора, которые включают оператор yield return или yield break.

Ответ 12

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

Ответ 13

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

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

Как упоминалось выше: "для параметра out, требуется, чтобы вызывающий метод назначал значение до того, как метод вернет."

Пример:

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

Ответ 14

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

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

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

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

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

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

Знаете ли вы?

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

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

    • Асинхронные методы, которые вы определяете с помощью модификатора async.
    • Методы Iterator, которые включают в себя инструкцию yield return или yield break.
  • Свойства не являются переменными и поэтому не могут быть переданы как выходные параметры.

Ответ 15

Дополнительные примечания относительно С# 7:
В С# 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})");
}

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

Ответ 16

Следует отметить, что in является допустимым ключевым словом с С# ver 7.2:

Модификатор параметра in доступен в С# 7.2 и более поздних версиях. Предыдущие версии генерировали ошибку компилятора CS8107 ("Функция" ссылки только для чтения "недоступна в С# 7.0. Пожалуйста, используйте языковую версию 7.2 или выше".) Чтобы настроить языковую версию компилятора, см. выбор языковой версии С#.

...

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

Ответ 17

Все еще чувствую потребность в хорошем резюме, вот что я придумал.

Резюме,

Когда мы находимся внутри функции, именно так мы указываем переменную для контроля доступа к данным,

in = R

out = должен W перед R

ref = R + W


Объяснение,

in

Функция может только ПРОЧИТАТЬ эту переменную.

out

Переменная не должна быть инициализирована первой, потому что,
Функция ДОЛЖНА НАПИСАТЬ до него, прежде чем ПРОЧИТАТЬ.

ref

Функция может READ/WRITE к этой переменной.


Почему он назван таковым?

Ориентируясь на то, где данные модифицируются,

in

Данные должны быть установлены только перед вводом (в) функции.

out

Данные должны быть установлены только перед выходом из функции.

ref

Данные должны быть установлены до ввода (в) функции.
Данные могут быть установлены до выхода из функции.