Коли використовувати в порівнянні з посиланням проти


383

Хтось запитав мене днями, коли вони повинні використовувати ключове слово параметр outзамість ref. Хоча я (я думаю) розумію різницю між ключовими словами refта outключовими словами (про що було задано раніше ) і найкращим поясненням здається, що ref== inі out, які приклади (гіпотетичні чи кодові) я завжди повинен використовувати, outа ні ref.

Оскільки refбільш загальне, чому ви взагалі хочете використовувати out? Це просто синтаксичний цукор?


18
Змінна, передана в користування, outне може бути прочитана, перш ніж її присвоїти refцього обмеження не має. Так ось це.
Корі Огберн

17
Коротше кажучи, refце вхід / вихід, а параметр out- лише вихід.
Тім С.

3
Чого саме ти не отримуєш?
tnw

4
Також outзмінні HAVE слід призначити у функції.
Корі Огберн

Дякую Корі. Але я вже не такий. Моя думка, що в цьому користь. Насправді мені потрібен приклад, який показує сценарій, коли ми можемо використовувати параметр ref, щоб досягти функціональності, якого неможливо досягти, використовуючи параметр та вірус.
Раджбір Сінгх

Відповіді:


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.
бричін

3
@brichins Будь ласка, зверніться до розділу "коментарі (доповнення до спільноти)" у вказаному вами посиланні . Це помилка, яка виправлена ​​в документації на VS 2008.
Бхарат Рам V

13
@brichins викликаному методу потрібно призначити значення, а не метод виклику. zverev.eugene Це те, що було виправлено в документації на VS 2008.
Segfault

72

Використовуйте для позначення того, що параметр не використовується, а лише встановлюється. Це допомагає абоненту зрозуміти, що ви завжди ініціалізуєте параметр.

Крім того, ref і out не стосуються лише типів значень. Вони також дозволяють скинути об'єкт, на який посилається тип посилання, в межах методу.


3
+1 Я не знав, що його можна використовувати і для референтних типів, приємна чітка відповідь, дякую
Дейл

@brichins: Ні, ти не можеш. outПараметри трактуються як непризначені при вході в функцію. Ви не зможете перевірити їх значення до тих пір, поки ви напевно не призначите якесь значення - взагалі немає способу використовувати значення, яке було встановлено під час виклику функції.
Ben Voigt

Правда, ви не можете отримати доступ до значення перед внутрішнім призначенням. Я мав на увазі той факт, що сам параметр може бути використаний пізніше в методі - він не заблокований. Чи слід це насправді робити чи ні - це інше обговорення (щодо дизайну); Я просто хотів зазначити, що це можливо. Дякуємо за роз’яснення.
бричін

2
@ ดาว: Він може використовуватися для типів посилань, оскільки, коли ви передаєте параметр типу посилання, те, що ви передаєте, є значенням посилання, а не самим об'єктом. Так що це все-таки прохідна цінність.
Тарік

38

Ви маєте рацію в тому, що семантично refнадає функцію як "в", так і "виходити", тоді як функціонує outлише "поза". Деякі речі, які слід врахувати:

  1. outвимагає, щоб метод, що приймає параметр ОБОВ'ЯЗКОВО, в якийсь момент перед поверненням призначив змінній значення. Цей шаблон ви знайдете в деяких класах зберігання даних ключових / значень, таких як Dictionary<K,V>, де у вас є такі функції TryGetValue. Ця функція приймає outпараметр, який утримує те, яке значення буде у випадку отримання. Не було б сенсу для абонента передавати значення в цю функцію, тому outвикористовується для гарантії того, що якесь значення буде в змінній після виклику, навіть якщо це не "реальні" дані (у випадку, TryGetValueколи ключ немає).
  2. outа refпараметри під час взаємодії з кодом interop змінюються по-різному

Крім того, важливо зазначити, що хоча типи посилань та типи значень відрізняються за характером їх значення, кожна змінна у вашому додатку вказує на місце пам'яті, яке містить значення , навіть для еталонних типів. Просто трапляється, що для посилальних типів значення, яке міститься в цьому місці пам'яті, є іншимрозташування пам'яті. Коли ви передаєте значення функції (або виконуєте будь-яке інше присвоєння змінної), значення цієї змінної копіюється в іншу змінну. Для типів значень це означає, що весь вміст типу копіюється. Для довідкових типів це означає, що місце пам'яті скопійовано. Так чи інакше, він створює копію даних, що містяться в змінній. Єдине реальне значення, яке стосується цього, стосується семантики присвоєння; при призначенні змінної або передачі за значенням (за замовчуванням), коли нове призначення зроблено оригінальній (або новій) змінній, це не впливає на іншу змінну. Що стосується типів посилань, так, зміни, внесені до екземплярадоступні з обох сторін, але це тому, що фактична змінна є лише вказівником на інше місце пам'яті; вміст змінної - місце пам'яті - насправді не змінився.

Передача з refключовим словом говорить про те, що і початкова змінна, і параметр функції насправді вказуватимуть на одне місце пам’яті. Це, знову ж таки, впливає лише на семантику призначення. Якщо нове значення присвоєно одній із змінних, то оскільки інше вказує на те саме місце пам'яті, нове значення відображатиметься з іншого боку.


1
Зауважте, що вимога про те, що викликаний метод присвоює значення параметру out, виконується компілятором c #, а не базовим IL. Отже, бібліотека, написана на VB.NET, може не відповідати цій конвенції.
jmoreno

Звучить, що ref є фактично еквівалентом символу перенаправлення в C ++ (*). Посилання на пропуск у C # має бути еквівалентним тому, що C / C ++ позначає подвійні покажчики (вказівник на вказівник), тому ref має перенаправити перший покажчик, дозволяючи викликаному методу отримати доступ до місця пам'яті фактичного об'єкта в контексті.
Приходьте

Насправді я б запропонував, що правильне TryGetValueвикористовувало б, refа не outявно, у разі не знаходження ключа.
NetMage

27

Це залежить від контексту компіляції (див. Приклад нижче).

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

MSDN попереджає :

Не плутайте поняття проходження шляхом посилання з поняттям еталонних типів. Дві концепції не однакові. Параметр методу можна змінювати за допомогою ref, незалежно від того, це тип значення або тип посилання. Немає боксу типу значення, коли воно передається посиланням.

Від офіційних Документів MSDN:

Ключове слово "out" спричиняє передачу аргументів шляхом посилання. Це схоже на ключове слово ref, за винятком того, що ref вимагає ініціалізації змінної перед передачею

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

Ми можемо переконатися, що вихід і 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. У програмі Out дані проходять лише один шлях від виклику до абонента. У цьому випадку, якщо Caller спробував надіслати дані абоненту, вони будуть пропущені / відхилені.

Якщо ви візуальна людина, то, будь ласка, подивіться це відео на YouTube, яке демонструє різницю практично https://www.youtube.com/watch?v=lYdcY5zulXA

Нижче зображення зображено відмінності більш наочно

C # Вихід 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
}

Дякуємо знука за вашу відповідь. Але ви можете мені пояснити, чому я не міг використати параметр для читання та запису?
Раджбір Сінгх

@RajbirSingh, оскільки outпараметри не обов'язково ініціалізуються, тому компілятор не дозволить вам читати з outпараметра, поки ви щось не напишете до нього.
zneak

zneak, я погодився з тобою. Але в наведеному нижче прикладі параметр out може бути використаний як read and write: string name = "myName"; private void OutMethod (вихідний рядок nameOut) {if (nameOut == "myName") {nameOut = "Метод Rajbir Singh in out"; }}
Раджбір Сінгх

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

Дякую @zneak. Ти абсолютно правий. Він не компілюється. Велике спасибі за мою допомогу, і тепер для мене це має сенс :)
Rajbir Singh

7

outє більш обмеженою версією ref.

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

Так outдозволяє:

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

де refпотрібно буде призначити a і b.


Якщо що, out- це менш обмежена версія. refмає "Передумова: змінна обов'язково присвоєна, Пост-умова: змінна визначена присвоєна", тоді як outмає лише `Пост- умова: змінна визначена безперечно". (І, як очікується, потрібно більше для реалізації функції з меншою кількістю попередніх умов)
Бен Войгт,

@BenVoigt: Звичайно, це залежить від того, в який бік ти дивишся :) Я думаю, я мав на увазі обмеження з точки зору гнучкості кодування (?).
леппі

7

Як це звучить:

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

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


змінна параметр 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для повернення трьох змінних за допомогою одного виклику методу. Зауважте, що третій аргумент призначений для null. Це дає можливість методам повертати значення необов'язково. 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

Просто для уточнення в коментарі ОП, що використання для відмови та виходу - це "посилання на тип значення або структуру, оголошену поза методом", яка вже була встановлена ​​невірно.

Розглянемо використання посилання на 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

чому ти коли-небудь хочеш використовувати?

Щоб повідомити іншим, що змінна буде ініціалізована, коли вона повернеться з викликаного методу!

Як було сказано вище: "для параметра" вихід "метод виклику необхідний для призначення значення до повернення методу ."

приклад:

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, а інший виводить аргумент.

  2. Ви не можете використовувати ключові слова для відмови та вибору для таких типів методів:

    • Методи асинхронізації, які ви визначаєте за допомогою модифікатора асинхронізації.
    • Ітераторні методи, які включають заяву про прибуток або перерву врожайності.
  3. Властивості не є змінними, і тому їх не можна передавати як параметри, що передаються.


4

Додаткові примітки щодо C # 7:
У C # 7 немає потреби попередньо визначати змінні, використовуючи. Отже, такий код:

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

Змінна не повинна бути ініціалізована спочатку, оскільки
функція ОБОВ'ЯЗКОВО ЗАПИСЬ до неї перед ЧИТАТИ .

ref

Функція може читати / писати до цієї змінної.


Чому його називають таким?

Зосередившись на тому, де дані змінюються,

in

Дані потрібно встановлювати лише до введення (в) функції.

out

Дані потрібно встановлювати лише перед тим, як залишити (вийти) функцію.

ref

Дані потрібно встановити перед введенням (в) функції.
Дані можуть бути встановлені перед тим, як залишити (вийти) функцію.


можливо (in / out / ref) слід перейменувати на (r / wr / rw). а може й ні, в / в - це приємніша метафора.
возитися

0

Слід зазначити, що inце дійсне ключове слово станом на C # ver 7.2 :

Модифікатор параметрів доступний у C # 7.2 та пізніших версіях. Попередні версії генерують помилку компілятора CS8107 ("Особливість" посилання для читання "недоступна в C # 7.0. Будь ласка, використовуйте мову версії 7.2 або новішої версії.") Для налаштування версії мови компілятора див. Вибір версії мови C #.

...

Ключове слово in приводить аргументи до передачі посилань. Це робить формальний параметр псевдонімом аргументу, який повинен бути змінною. Іншими словами, будь-яка операція над параметром робиться на аргументі. Це як ключові слова ref або out, за винятком того, що аргументи не можуть бути змінені викликаним методом. Тоді як аргументи ref можуть бути модифіковані, аргументи з виправлень повинні бути модифіковані методом, що викликається, і ці модифікації можна побачити в контексті виклику.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.