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

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

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

15 ответов

Решение

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

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

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

Как незначительное отличие, выходной параметр не нужно инициализировать.

Пример для out:

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

где GetBothNames - это метод для извлечения двух значений атомарно, метод не изменит поведения, какими бы ни были a и b. Если вызов поступает на сервер на Гавайях, копирование начальных значений отсюда на Гавайи является пустой тратой пропускной способности. Аналогичный фрагмент с использованием 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 обеспечивает как "в" и "выход" функциональность, тогда как out только обеспечивает "вне" функциональность. Есть несколько вещей, которые следует учитывать:

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

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

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

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

out а также ref оба обозначают переменную, передаваемую по ссылке, но пока ref требует, чтобы переменная была инициализирована перед передачей, что может быть важным отличием в контексте маршалинга (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:

  • out:

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: нет операции, ldloc: загрузить локальный, stloc: локальный стек, ldarg: загрузить аргумент, bs.s: перейти к цели....

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

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

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

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

Ниже изображение показывает различия более наглядно

C# Out Vs Ref

Вам нужно использовать 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 возвращает 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,

В следующей версии 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
}

Как пользоваться in или же out или же ref в C#?

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

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

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

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

Резюме,

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

in = R

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

ref = R + W


Объяснение,

in

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

out

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

ref

Функция может ЧИТАТЬ / ЗАПИСАТЬ в эту переменную.


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

Сосредоточившись на том, где изменяются данные,

in

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

out

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

ref

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

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

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

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

out более ограниченная версия ref,

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

Так out позволяет делать:

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

где ref потребует, чтобы a и b были назначены.

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

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

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

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

Какая польза от "ref" для ссылочных типов?

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

Вы знали?

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

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

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

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

Дополнительные примечания относительно 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.

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

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

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

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

пример:

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

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

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

...

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

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