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


383

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

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


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

17
Короче говоря, refдля входа / выхода, в то время outкак параметр только для выхода.
Тим С.

3
Что именно ты не получаешь?
ТЯО

4
Также outпеременные должны быть назначены в функции.
Кори Огберн

Спасибо Кори. Но я уже не тот. Я хочу сказать, что в этом выгода. На самом деле мне нужен пример, который показывает сценарий, в котором мы можем использовать параметр ref для достижения функциональности, которая не может быть достигнута с помощью параметра out и наоборот.
Раджбир Сингх

Ответы:


399

Вы должны использовать, 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);

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


5
«Это не совсем так». - не могли бы вы объяснить лучше, что вы имеете в виду?
peterchen

3
Вы не хотите использовать refдля значений по умолчанию.
C.Evenhuis

155
Для потомков: еще одно различие, о котором никто не упомянул, как указано здесь ; для outпараметра вызывающий метод должен присвоить значение до его возврата. - вам не нужно ничего делать с параметром ref.
Brichins

3
@brichins Пожалуйста, обратитесь к разделу «комментарии (дополнения сообщества)» по указанной вами ссылке . Это ошибка, которая исправлена ​​в документации VS 2008.
Бхарат Рам V

13
@brichins вызываемый метод должен присваивать значение, а не вызывающий метод. zverev.eugene это то, что было исправлено в документации VS 2008.
Сегфо

72

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

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


3
+1 Я не знал, что это можно использовать и для ссылочных типов, хороший четкий ответ, спасибо
Dale

@brichins: Нет, ты не можешь. outпараметры рассматриваются как неназначенные при входе в функцию. Вы не сможете проверить их значение до тех пор, пока не определите какое-то определенное значение - нет никакого способа использовать значение, которое имел параметр при вызове функции.
Бен Фойгт

Правда, вы не можете получить доступ к значению до внутреннего назначения. Я имел в виду тот факт, что сам параметр можно использовать позже в методе - он не заблокирован. Должно ли это быть сделано на самом деле или нет - другое обсуждение (по дизайну); Я просто хотел указать, что это возможно. Благодарю за разъяснение.
Brichins

2
@ ดาว: Его можно использовать со ссылочными типами, потому что, когда вы передаете параметр ссылочного типа, вы передаете значение ссылки, а не сам объект. Так что это все равно по стоимости.
Тарик

38

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

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

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

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


1
Обратите внимание, что требование к вызываемому методу присваивать значение параметру out применяется компилятором c #, а не базовым IL. Таким образом, библиотека, написанная на VB.NET, может не соответствовать этому соглашению.
Jmoreno

Похоже, что ссылка на самом деле является эквивалентом символа разыменования в C ++ (*). Ссылка на passby в C # должна быть эквивалентна тому, что C / C ++ называет двойными указателями (указатель на указатель), поэтому ref должен разыменовать 1-й указатель, предоставляя вызываемому методу доступ к расположению памяти фактического объекта в контексте.
ComeIn

Я бы на самом деле предложил бы правильное TryGetValueиспользование, refа не outявно в случае не нахождения ключа.
NetMage

27

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

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

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

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

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

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

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

Мы можем проверить, что 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 )


23

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

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

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

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

C # Out Vs Ref


1
one-way, здесь two-wayмогут быть неправильно использованы термины. На самом деле они оба двусторонние, однако их концептуальное поведение различается в зависимости от параметров и значений
ibubi

17

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

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

Спасибо zneak за ваш ответ. Но можете ли вы объяснить мне, почему я не мог использовать для чтения и записи параметра?
Раджбир Сингх

@RajbirSingh, поскольку outпараметры не обязательно были инициализированы, поэтому компилятор не позволит вам читать из outпараметра, пока вы что-то не напишите в него.
zneak

zneak, я с тобой согласен. Но в приведенном ниже примере выходной параметр может использоваться для чтения и записи: string name = "myName"; private void OutMethod (out string nameOut) {if (nameOut == "myName") {nameOut = "Rajbir Singh in out method"; }}
Раджбир Сингх

1
@RajbirSingh, ваш пример не компилируется. Вы не можете прочитать nameOutв своем ifзаявлении, потому что ничего не было назначено раньше.
zneak

Спасибо @zneak. Ты абсолютно прав. Это не компилируется. Большое спасибо за мою помощь, и теперь это имеет смысл для меня :)
Раджбир Сингх

7

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

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

Итак, outпозволяет сделать:

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

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


Во всяком случае, outэто менее ограниченная версия. refимеет «Предусловие: переменная определенно назначена, Постусловие: переменная определенно назначено», в то время как outимеет только «Постусловие: переменная определенно назначена». (И, как и следовало ожидать, требуется больше реализации функции с меньшим количеством предварительных условий)
Бен Фойгт,

@BenVoigt: Думаю, это зависит от того, в каком направлении вы смотрите :) Я думаю, что имел в виду ограничение с точки зрения гибкости кодирования (?).
Леппи

7

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

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

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


Переменная параметра out может получить значение, прежде чем передать его в метод.
Бенс Вегерт

6

Вы можете использовать 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;
    }
}

6

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

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

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

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

5

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

4

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


4

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

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

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

пример:

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

4

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

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

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

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

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

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

Вы знали?

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

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

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


4

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


4

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

Резюме,

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

in = R

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

ref = R + W


Объяснение,

in

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

out

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

ref

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


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

Сосредоточение внимания на том, где данные модифицируются,

in

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

out

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

ref

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


возможно (in / out / ref) следует переименовать в (r / wr / rw). а может и нет, вход / выход - более приятная метафора.
возиться

0

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

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

...

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.