Когда использовать в отношении refs vs out - PullRequest
362 голосов
/ 04 октября 2009

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

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

Ответы [ 16 ]

383 голосов
/ 04 октября 2009

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

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

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

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

Пример для 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);

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

71 голосов
/ 04 октября 2009

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

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

36 голосов
/ 04 октября 2009

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

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

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

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

25 голосов
/ 24 сентября 2012

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

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:

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

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

20 голосов
/ 04 сентября 2015

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

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

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

На рисунке ниже визуально показаны различия

C# Out Vs Ref

17 голосов
/ 26 декабря 2013

Вам нужно использовать 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
}
7 голосов
/ 31 января 2016

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

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

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

7 голосов
/ 04 октября 2009

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

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

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

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

, где ref требует назначения a и b.

6 голосов
/ 08 декабря 2015

Вы можете использовать контекстное ключевое слово 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;
    }
}
5 голосов
/ 14 сентября 2018

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

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

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

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