Когда использовать в отношении 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
только обеспечивает "вне" функциональность. Есть несколько вещей, которые следует учитывать:
out
требует, чтобы метод, принимающий параметр, ДОЛЖЕН в какой-то момент перед возвратом присвоить значение переменной. Вы найдете этот шаблон в некоторых классах хранения данных ключ / значение, таких какDictionary<K,V>
где у вас есть такие функции, какTryGetValue
, Эта функция занимаетout
параметр, который содержит то, что значение будет, если получено. Для вызывающей стороны не имеет смысла передавать значение в эту функцию, поэтомуout
используется для гарантии того, что какое-то значение будет в переменной после вызова, даже если это не "реальные" данные (в случаеTryGetValue
где ключа нет).out
а такжеref
параметры обрабатываются по-разному при работе с кодом взаимодействия
Кроме того, кроме того, важно отметить, что, хотя ссылочные типы и типы значений различаются по характеру своего значения, каждая переменная в вашем приложении указывает на область памяти, в которой хранится значение, даже для ссылочных типов. Просто случается, что со ссылочными типами значение, содержащееся в этом месте памяти, является другим местом памяти. Когда вы передаете значения в функцию (или делаете любое другое присвоение переменной), значение этой переменной копируется в другую переменную. Для типов значений это означает, что все содержимое типа копируется. Для ссылочных типов это означает, что область памяти копируется. В любом случае, он создает копию данных, содержащихся в переменной. Единственное реальное значение, которое это имеет, касается семантики присваивания; при присваивании переменной или передаче по значению (по умолчанию), когда новое присваивание выполняется исходной (или новой) переменной, это не влияет на другую переменную. В случае ссылочных типов, да, изменения, внесенные в экземпляр, доступны с обеих сторон, но это потому, что фактическая переменная является просто указателем на другое место в памяти; содержимое переменной - ячейка памяти - фактически не изменилось.
Проходя с ref
Ключевое слово говорит, что как исходная переменная, так и параметр функции будут фактически указывать на одну и ту же область памяти. Это, опять же, влияет только на семантику присваивания. Если одной из переменных назначено новое значение, то, поскольку другие указывают на то же место в памяти, новое значение будет отражено на другой стороне.
Это зависит от контекста компиляции (см. Пример ниже).
out
а также ref
оба обозначают переменную, передаваемую по ссылке, но пока ref
требует, чтобы переменная была инициализирована перед передачей, что может быть важным отличием в контексте маршалинга (Interop: UmanagedToManagedTransition или наоборот)
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.
- Его следует использовать только тогда, когда мы ожидаем несколько выходных данных от функции или метода. Мысль о структурах может быть также хорошим вариантом для того же.
- REF и OUT - это ключевые слова, которые определяют, как данные передаются от вызывающего к вызываемому и наоборот.
- В REF данные проходят в двух направлениях. От звонящего к вызываемому и наоборот.
- Входящие данные проходят только один путь от вызываемого абонента к вызывающему. В этом случае, если вызывающий абонент попытался отправить данные вызываемому абоненту, они будут пропущены / отклонены.
Если вы визуальный человек, посмотрите это видео на YouTube, которое демонстрирует разницу практически https://www.youtube.com/watch?v=lYdcY5zulXA
Ниже изображение показывает различия более наглядно
Вам нужно использовать 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" для ссылочных типов?
Вы можете изменить данную ссылку на другой экземпляр.
Вы знали?
Хотя ключевые слова ref и out вызывают различное поведение во время выполнения, они не считаются частью сигнатуры метода во время компиляции. Поэтому методы не могут быть перегружены, если единственное отличие состоит в том, что один метод принимает аргумент ref, а другой - аргумент out.
Вы не можете использовать ключевые слова ref и out для следующих методов:
- Асинхронные методы, которые вы определяете с помощью модификатора async.
- Методы итератора, которые включают в себя возврат доходности или оператор разрыва доходности.
Свойства не являются переменными и поэтому не могут быть переданы как параметры.
Просто чтобы пояснить комментарий 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 должны быть изменены вызываемым методом, и эти модификации наблюдаемы в контексте вызова.