Яка різниця між ключовими словами 'ref' та 'out'?


891

Я створюю функцію, де мені потрібно передати об’єкт, щоб він міг бути змінений функцією. Яка різниця між:

public void myFunction(ref MyClass someClass)

і

public void myFunction(out MyClass someClass)

Що я повинен використовувати і чому?


69
Ви: Мені потрібно передати об’єкт, щоб його можна було змінити. Схоже, MyClassце був би classтип, тобто тип посилання. У такому випадку об'єкт, який ви передаєте, може бути змінений за myFunctionдопомогою ключового слова even ref/ no / out. myFunctionотримає нове посилання, яке вказує на один і той же об'єкт, і він може змінювати цей самий об'єкт скільки завгодно. Різниця, яку refзробило б ключове слово, полягала б у тому, що myFunctionотримали те саме посилання на той самий об’єкт. Це було б важливо лише, якби myFunctionзмінити посилання, щоб вказати на інший об'єкт.
Джеппе Стіг Нільсен

3
Мене спантеличує кількість заплутаних відповідей тут, коли @ AnthonyKolesov's цілком досконалий.
o0 '.

Оголошення методу виходу корисно, коли ви хочете, щоб метод повертав кілька значень. Один аргумент може бути присвоєний нулю. Це дає можливість методам повертати значення необов'язково.
Євграф Андрійович Живаго

Ось пояснено з Прикладом Це більш зрозуміло :) dotnet-tricks.com/Tutorial/csharp/…
Прагідський бог

2
@ JeppeStigNielsen, технічно, є (єдиною) правильною відповіддю на власне питання ОП. Щоб передати об’єкт у метод, щоб метод міг модифікувати об'єкт , просто передайте (посилання на) об'єкт в метод за значенням. Зміна об'єкта в методі через аргумент об'єкта модифікує вихідний об'єкт , навіть незважаючи на те, що метод містить власну окрему змінну (яка посилається на той самий об'єкт).
David R Tribble

Відповіді:


1160

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

Тож поки refдвостороння, outє лише вихідною.


270
Ще одна цікава річ, специфічна для того, щоб функція мала призначити вихідний параметр. Заборонено залишати це без призначення.
Даніель Ервікер

7
чи 'ref' застосовується лише для типу значень? Оскільки тип посилання завжди передається за посиланням.
несправний

3
Так. Типи вартості, включаючи конструкції
Rune Grimstad

17
@faulty: Ні, ref не стосується лише типів значень. ref / out - це як покажчики на C / C ++, вони мають справу з місцем пам'яті об'єкта (опосередковано в C #) замість прямого об'єкта.
Чет

52
@faulty: Типи посилань завжди мають протизаконне значення, якщо вони не використовуються у специфікаторі ref. Якщо встановити myval = somenewval, ефект є лише в тій області функцій. Ключове слово ref дозволить вам змінити myval, щоб вказати на somenewval.
JasonTrue

535

У refмодифікатор означає , що:

  1. Значення вже встановлено і
  2. Метод може його читати і змінювати.

У outмодифікатор означає , що:

  1. Значення не встановлено і його неможливо прочитати методом, поки воно не встановлено.
  2. Метод повинен встановити його перед поверненням.

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

5
Від MSDN: Параметр ref повинен бути ініціалізований перед використанням, тоді як вихідний параметр не повинен бути явно ініціалізований перед передачею, а будь-яке попереднє значення ігнорується.
Шива Кумар

1
З out, чи можна його читати взагалі в методі, перш ніж він буде встановлений цим методом, якщо він був ініціалізований до виклику методу? Я маю на увазі, чи може метод виклику читати те, що метод виклику передається йому як аргумент?
Panzercrisis

3
Panzercrisis, для "out", викликаний метод може прочитати, якщо він уже встановлений. але він повинен встановити його знову.
robert jebakumar2

146

Скажімо, Дом з'являється в кабінеті Петра про доповідь про звіти TPS.

Якби Дом був аргументом, він мав би надруковану копію записки.

Якби Дом не був аргументом, він змусив би Петра надрукувати нову копію пам’ятки, яку він взяв із собою.


54
ref Дом написав би репортаж олівцем, щоб Пітер міг його модифікувати
Дібстер

6
@Deebster Ви знаєте, що метафора ніколи вам нічого не робила, чому ви повинні так її катувати? ;)
Майкл Блекберн

21
розважаючи, але навчаючи, stackoverflow потребує таких публікацій, як цей
Френк Вісаджо

2
На всякий випадок, якщо хтось вважає цю відповідь лише напівсмішною, перегляньте фільм "Офісні місця".
показНазви

і Бос Дом і Петерс стояли б перед Домом (як аргумент), змушуючи обох працювати над тим, щоб надрукувати його заново, поки Пітер не видасть Дом роздруківку
Патрік Артнер

57

Я спробую спробувати свої пояснення:

Я думаю, ми розуміємо, як працюють цінні типи правильно? Типи значень: (int, long, структура тощо). Коли ви надсилаєте їх до функції без команди ref, вона копіює дані . Все, що ви робите з цими даними у функції, впливає лише на копію, а не на оригінал. Команда ref надсилає дані АКТУАЛЬНІ, і будь-які зміни вплинуть на дані поза функцією.

Підключіть до заплутаної частини посилання:

Дозволяє створити тип посилання:

List<string> someobject = new List<string>()

Коли ви створюєте об'єкт , створюються дві частини:

  1. Блок пам'яті, який містить дані для якогось об'єкта .
  2. Посилання (покажчик) на цей блок даних.

Тепер , коли ви посилаєте в SomeObject в метод без реф він скопіює опорний покажчик, а НЕ даних. Отже, у вас зараз це:

(outside method) reference1 => someobject
(inside method)  reference2 => someobject

Дві посилання, що вказують на один і той же об’єкт. Якщо ви модифікуєте властивість на якомусь об’єкті за допомогою reference2, це вплине на ті самі дані, на які вказує посилання1.

 (inside method)  reference2.Add("SomeString");
 (outside method) reference1[0] == "SomeString"   //this is true

Якщо ви скасуєте reference2 або вкажете його на нові дані, це не вплине на reference1, а також на reference11.

(inside method) reference2 = new List<string>();
(outside method) reference1 != null; reference1[0] == "SomeString" //this is true

The references are now pointing like this:
reference2 => new List<string>()
reference1 => someobject

Тепер то , що відбувається , коли ви посилаєте SomeObject по вих до методу? Фактична посилання на SomeObject відправляється до методу. Отже, у вас є лише одне посилання на дані:

(outside method) reference1 => someobject;
(inside method)  reference1 => someobject;

Але що це означає? Він діє точно так само, як надсилати який-небудь об’єкт не за посиланням, за винятком двох головних речей:

1) Якщо ви зведете назовні посилання всередині методу, воно обнулять його поза методом.

 (inside method)  reference1 = null;
 (outside method) reference1 == null;  //true

2) Тепер ви можете вказувати посилання на зовсім інше місце розташування даних, а посилання поза функцією тепер буде вказувати на нове місцезнаходження даних.

 (inside method)  reference1 = new List<string>();
 (outside method) reference1.Count == 0; //this is true

Ви маєте на увазі, що в кінцевому підсумку (в такому випадку) є лише одна посилання на дані, але два псевдоніми для них. Правильно?
Садік

3
Запропонований за чітке пояснення. Але я думаю, що це не відповідає на питання, оскільки це не пояснює різницю між параметрами refта outпараметрами.
Джойс Бабу

1
Дивовижний. Ви можете пояснити те саме, що і для outключового слова?
Асиф Муштак

28

ісй в і поза .

Ви повинні використовувати outперевагу там, де це достатньо для ваших вимог.


не зовсім, як відповідь прийнятої відповіді, якщо спрямоване і марне ігнорування типів значень, якщо вони не передаються назад.
kenny

@kenny: Чи можете ви трохи уточнити, будь ласка - тобто, які слова ви змінили б, щоб зберегти дух відповіді, але усунете неточність, яку ви сприймаєте? Моя відповідь - це не шалений здогад від новачків, але поспішність (лайливість, помилки) у вашому коментарі, здається, припускає, що вона є. Метою є створення способу мислення про різницю з найменшою кількістю слів.
Рубен Бартелинк

(BTW Мені знайомі типи значень, типи посилань, проходження посилань, проходження за значенням, COM та C ++, якщо вам
здасться

1
Посилання на об'єкти передаються за значенням (за винятком випадків використання ключового слова "ref" або "out"). Подумайте про об'єкти як ідентифікаційні номери. Якщо змінна класу містить "Об'єкт № 1943" і передає цю змінну за значенням у звичайну програму, ця рутина може вносити зміни до Об'єкту № 1943, але вона не може вказувати змінну на щось інше, ніж на "Об'єкт № 1943". Якщо змінна була передана за посиланням, підпрограма може зробити змінну крапку "Об'єкт № 5441".
supercat

1
@supercat: Мені подобається ваше пояснення по відношенню до val (і ця подальша аналогія). Я думаю, що Кенні насправді не потребує нічого поясненого йому (відносно) заплутаного, як і його коментарі. Я б хотів, щоб ми могли просто видалити ці прокляті коментарі, хоча вони просто заплутують усіх. Першопричиною всієї цієї дурниці є те, що Кенні неправильно прочитав мою відповідь і ще не вказав на одне слово, яке слід додати / видалити / замінити. Ніхто з трьох нас нічого не дізнався з дискусії, про яку ми ще не знали, а в іншій відповіді є смішна кількість відгуків.
Рубен Бартелінк

18

з:

У C # метод може повернути лише одне значення. Якщо ви хочете повернути більше одного значення, ви можете використовувати ключове слово out. Вихідний модифікатор повертається як зворотний посилання. Найпростіша відповідь полягає в тому, що ключове слово "out" використовується для отримання значення методу.

  1. Вам не потрібно ініціалізувати значення у функції виклику.
  2. Ви маєте призначити значення в викликаній функції, інакше компілятор повідомить про помилку.

посилання:

У C #, коли ви передаєте тип значення, такий як int, float, double тощо як аргумент параметру методу, воно передається за значенням. Тому, якщо ви змінюєте значення параметра, воно не впливає на аргумент у виклику методу. Але якщо ви позначите параметр ключовим словом «ref», він відобразиться у фактичній змінній.

  1. Вам потрібно ініціалізувати змінну, перш ніж викликати функцію.
  2. Не обов'язково призначати якесь значення параметру ref у методі. Якщо ви не змінюєте значення, що потрібно позначати як "ref"?

"У C # метод може повернути лише одне значення. Якщо ви хочете повернути більше одного значення, ви можете використовувати ключове слово out." Ми також можемо використовувати "ref" для повернення значення. Отже, ми можемо використовувати як ref, так і out, якщо хочемо повернути кілька значень з методу?
Нед

1
У c # 7 ви можете повернути кілька значень за допомогою ValueTuples.
Іман Бахрампар

13

Розширення собаки, приклад кота. Другий метод із ref змінює об'єкт, на який посилається абонент. Звідси "Кіт" !!!

    public static void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog". 
        Bar(ref myObject);
        Console.WriteLine(myObject.Name); // Writes "Cat". 
    }

    public static void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

    public static void Bar(ref MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

8

Оскільки ви передаєте тип посилання (клас), не потрібно використовувати, refоскільки за замовчуванням передається лише посилання на фактичний об'єкт, і тому ви завжди змінюєте об'єкт за посиланням.

Приклад:

public void Foo()
{
    MyClass myObject = new MyClass();
    myObject.Name = "Dog";
    Bar(myObject);
    Console.WriteLine(myObject.Name); // Writes "Cat".
}

public void Bar(MyClass someObject)
{
    someObject.Name = "Cat";
}

Доки ви переходите в клас, вам не доведеться користуватися, refякщо ви хочете змінити об'єкт всередині свого методу.


5
Це працює лише в тому випадку, якщо новий об’єкт не створюється і не повертається. Коли створюється новий об'єкт, посилання на старий об'єкт буде втрачено.
etsuba

8
Це неправильно - спробуйте наступне: add someObject = nullto Barend Execute. Ваш код буде спрацьовувати нормально, оскільки скасовується лише Barпосилання на примірник. Тепер перейдіть Barна Bar(ref MyClass someObject)і виконайте знову - ви отримаєте, NullReferenceExceptionоскільки Fooпосилання на екземпляр теж було анульовано.
Кіт

8

refі outповодяться аналогічно, за винятком наступних відмінностей.

  • refзмінна повинна бути ініціалізована перед використанням. outзмінна може використовуватися без призначення
  • outПараметр повинен розглядатися як непризначене значення функцією, яка його використовує. Отже, ми можемо використовувати ініціалізований outпараметр у коді виклику, але значення буде втрачено при виконанні функції.

8

Для тих, хто вчиться на прикладі (як я), ось що говорить Ентоні Колесов .

Я створив кілька мінімальних прикладів реф, поза та інших, щоб проілюструвати суть. Я не висвітлюю найкращих практик, лише приклади, щоб зрозуміти відмінності.

https://gist.github.com/2upmedia/6d98a57b68d849ee7091


6

"Бейкер"

Це тому, що перший змінює ваш рядковий посилання на вказівку "Бейкер". Змінення посилання можливо, оскільки ви передали його за допомогою ключового слова ref (=> посилання на посилання на рядок). Другий виклик отримує копію посилання на рядок.

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

string s = "Able";

тоді s - посилання на рядок класу, який містить текст "Able"! Ще одне призначення тій самій змінній через

s = "Baker";

не змінює початковий рядок, а просто створює новий екземпляр і нехай вказує s на цей екземпляр!

Ви можете спробувати з наступним невеликим прикладом коду:

string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);

Що ти очікуєш? Що ви отримаєте, все ще є "Здатним", оскільки ви просто встановите посилання в s на інший екземпляр, а s2 вказує на початковий екземпляр.

EDIT: рядок також непорушний, що означає, що просто немає методу чи властивості, що модифікує існуючий екземпляр рядка (ви можете спробувати знайти його в документах, але ви не будете довершені :-)). Усі способи маніпулювання рядками повертають новий екземпляр рядка! (Ось чому ви часто отримуєте кращі показники, використовуючи клас StringBuilder)


1
Саме так. Тож не зовсім вірно говорити "Оскільки ви переходите в тип посилання (клас), не потрібно використовувати ref".
Пол Мітчелл

Теоретично це правильно сказати, оскільки він написав "так, щоб його можна було змінити", що неможливо на рядках. Але через незмінні об'єкти "ref" і "out" дуже корисні також для еталонних типів! (.Net містить багато непорушних класів!)
ммммммммм

Так, ви праві. Я не думав про незмінні об'єкти, як струни, оскільки більшість об'єктів є змінними.
Албік

1
Ну, це дивовижна відповідь, яку слід побачити в LQP; з цим немає нічого поганого, за винятком того, що це, здається, довгий і ретельний відповідь на інший коментар (оскільки в первинному питанні не згадується Ебл і Бейкер в жодній його редакції), як би це був форум. Я здогадуюсь, це було насправді не розібране ще колись назад.
Натан Туггі

6

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


out повідомляє компілятору, що ініціалізація об'єкта є відповідальністю функції, функція повинна призначити вихідний параметр. Заборонено залишати це без призначення.


5

Вихід: Оператор повернення може використовуватися для повернення лише одного значення функції. Однак, використовуючи вихідні параметри, ви можете повернути два значення з функції. Вихідні параметри схожі на опорні параметри, за винятком того, що вони передають дані з методу, а не в них.

Наступний приклад ілюструє це:

using System;

namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void getValue(out int x )
      {
         int temp = 5;
         x = temp;
      }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;

         Console.WriteLine("Before method call, value of a : {0}", a);

         /* calling a function to get the value */
         n.getValue(out a);

         Console.WriteLine("After method call, value of a : {0}", a);
         Console.ReadLine();

      }
   }
}

ref: Довідковий параметр - це посилання на місце пам'яті змінної. Коли ви передаєте параметри за посиланням, на відміну від значущих параметрів, для цих параметрів не створюється нове місце зберігання. Референтні параметри представляють те саме місце пам'яті, що і фактичні параметри, що надходять до методу.

У C # ви оголошуєте опорні параметри за допомогою ключового слова ref. Наступний приклад демонструє це:

using System;
namespace CalculatorApplication
{
   class NumberManipulator
   {
      public void swap(ref int x, ref int y)
      {
         int temp;

         temp = x; /* save the value of x */
         x = y;   /* put y into x */
         y = temp; /* put temp into y */
       }

      static void Main(string[] args)
      {
         NumberManipulator n = new NumberManipulator();
         /* local variable definition */
         int a = 100;
         int b = 200;

         Console.WriteLine("Before swap, value of a : {0}", a);
         Console.WriteLine("Before swap, value of b : {0}", b);

         /* calling a function to swap the values */
         n.swap(ref a, ref b);

         Console.WriteLine("After swap, value of a : {0}", a);
         Console.WriteLine("After swap, value of b : {0}", b);

         Console.ReadLine();

      }
   }
}

4

ref і out працюють так само, як проходження посилання та проходження вказівниками, як у C ++.

Для посилання аргумент повинен оголосити та ініціалізувати.

Для, аргумент повинен оголошуватися, але може бути, а може ініціалізуватися

        double nbr = 6; // if not initialized we get error
        double dd = doit.square(ref nbr);

        double Half_nbr ; // fine as passed by out, but inside the calling  method you initialize it
        doit.math_routines(nbr, out Half_nbr);

1
Ви можете оголосити змінну інлайн: out double Half_nbr.
Себастьян Хофманн

4

Час авторства:

(1) Ми створюємо метод виклику Main()

(2) він створює об'єкт List (який є об'єктом еталонного типу) і зберігає його у змінній myList.

public sealed class Program 
{
    public static Main() 
    {
        List<int> myList = new List<int>();

Під час виконання:

(3) Виконання часу виділяє пам'ять у стеці на # 00, достатньо широку, щоб зберігати адресу (# 00 = myList, оскільки імена змінних дійсно просто псевдоніми для місць розташування пам'яті)

(4) Під час виконання створюється об’єкт списку в купі в пам'яті #FF (усі ці адреси, наприклад, збережені)

(5) Потім час виконання зберігатиме початкову адресу #FF об'єкта під № 00 (або словами, зберігає вказівник об'єкта List у вказівнику myList)

Повернутися до часу створення:

(6) Потім ми передаємо об'єкт List як аргумент myParamListвикликаному методу modifyMyListі присвоюємо йому новий об'єкт List

List<int> myList = new List<int>();

List<int> newList = ModifyMyList(myList)

public List<int> ModifyMyList(List<int> myParamList){
     myParamList = new List<int>();
     return myParamList;
}

Під час виконання:

(7) Виконання часу запускає процедуру виклику для виклику методу і як частина його перевіряє тип параметрів.

(8) Після знаходження опорного типу, він виділяє пам'ять на стек №04 для випромінення змінної параметра myParamList.

(9) Потім він також зберігає значення #FF у ньому.

(10) Виконання часу створює об'єкт списку в купі в пам’яті № 004 і замінює #FF в # 04 цим значенням (або відміняє оригінальний об’єкт List і вказує на новий об’єкт List у цьому методі)

Адреса в # 00 не змінена і зберігає посилання на #FF (або вихідний myListпокажчик не порушений).


Посилань Ключове слово є директива компілятора , щоб пропустити генерацію коду під час виконання для (8) і (9) , що означає , що не буде ніякого розподілу купи для параметрів методу. Він буде використовувати оригінальний покажчик №00 для роботи над об'єктом на #FF. Якщо оригінальний покажчик не ініціалізований, час виконання зупиняється, скарга не може продовжуватися, оскільки змінна не ініціалізується

З ключових слів є директива компілятора , який в значній мірі так само , як вих з невеликою зміною в (9) і (10). Компілятор очікує, що аргумент буде неініціалізованим і продовжить (8), (4) та (5) створити об’єкт у купі та зберегти його вихідну адресу у змінній аргументу. Жодна неініціалізована помилка не буде викинута, і будь-яка попередня збережена посилання буде втрачена.


3

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

  • Вам не потрібно ref , або outякщо всі , що ви збираєтеся зробити , це змінити речі всередині в MyClassразі , який приймає в якості аргументу someClass.

    • Метод виклику побачить зміни, як someClass.Message = "Hello World"ви користуєтесь ref, outчи нічого
    • Запис someClass = new MyClass()всередину myFunction(someClass)витісняє об'єкт, що бачиться лише someClassв межах myFunctionметоду. Метод виклику все ще знає про створений ним оригінальний MyClassекземпляр і передається вашому методу
  • Вам потрібно ref або outякщо ви плануєте замінити someClassвихід на абсолютно новий об'єкт і хочете, щоб метод виклику бачив зміни

    • Введення someClass = new MyClass()всередині myFunction(out someClass)змінює об'єкт, який бачить метод, який викликавmyFunction

Інші програмісти існують

І вони хочуть знати, що ви збираєтеся робити зі своїми даними. Уявіть, що ви пишете бібліотеку, яку використовуватимуть мільйони розробників. Ви хочете, щоб вони знали, що ви збираєтеся робити зі своїми змінними, коли вони викликають ваші методи

  • Використовуючи refробить вислів "Передати змінну, присвоєну якомусь значенню, коли ви викликаєте мій метод. Будьте в курсі, що я міг би змінити його на щось інше цілком під час мого методу. Не сподівайтеся, що ваша змінна буде вказувати на старий об'єкт коли я закінчу "

  • Використовуючи outробить вислів "Передати змінну заповнювача заповнення моєму методу. Неважливо, має він значення чи ні; компілятор змусить мене призначити його новим значенням. Я абсолютно гарантую, що об'єкт, на який вказує ваш змінна, перш ніж ви викликали мій метод, буде відрізнятися від часу, коли я закінчу

До речі, в C # 7.2 inтеж є модифікатор

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


Microsoft зробила це .TryParseметодами для числових типів:

int i = 98234957;
bool success = int.TryParse("123", out i);

Позначаючи параметр, коли outвони активно декларують тут, "ми обов'язково змінимо ваше кропітко продумане значення 98234957 на щось інше"

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

public void PoorlyNamedMethod(out SomeClass x)

Ви можете бачити, що це out, і, таким чином, ви можете знати, що якщо ви витрачаєте години на стискання чисел, створюючи ідеальний SomeClass:

SomeClass x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

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


3

Для тих, хто шукає лаконічну відповідь.

І ключові слова, refі outключові слова використовуються для проходження повз reference.


Змінна refключового слова повинна мати значення або повинна посилатися на об'єкт або null до його передачі.


На відміну від цього ref, змінна outключового слова повинна мати значення або повинна посилатися на об’єкт або null після його передачі, а також не потрібно мати значення або посилатися на об'єкт перед передачею.


2

Щоб проілюструвати безліч чудових пояснень, я розробив таку консольну програму:

using System;
using System.Collections.Generic;

namespace CSharpDemos
{
  class Program
  {
    static void Main(string[] args)
    {
      List<string> StringList = new List<string> { "Hello" };
      List<string> StringListRef = new List<string> { "Hallo" };

      AppendWorld(StringList);
      Console.WriteLine(StringList[0] + StringList[1]);

      HalloWelt(ref StringListRef);
      Console.WriteLine(StringListRef[0] + StringListRef[1]);

      CiaoMondo(out List<string> StringListOut);
      Console.WriteLine(StringListOut[0] + StringListOut[1]);
    }

    static void AppendWorld(List<string> LiStri)
    {
      LiStri.Add(" World!");
      LiStri = new List<string> { "¡Hola", " Mundo!" };
      Console.WriteLine(LiStri[0] + LiStri[1]);
    }

    static void HalloWelt(ref List<string> LiStriRef)
     { LiStriRef = new List<string> { LiStriRef[0], " Welt!" }; }

    static void CiaoMondo(out List<string> LiStriOut)
     { LiStriOut = new List<string> { "Ciao", " Mondo!" }; }
   }
}
/*Output:
¡Hola Mundo!
Hello World!
Hallo Welt!
Ciao Mondo!
*/
  • AppendWorld: Передається копія StringListімені LiStri. На початку методу ця копія посилається на оригінальний список і тому може бути використана для зміни цього списку. Пізніше LiStriпосилається на інший List<string>об’єкт всередині методу, який не впливає на вихідний список.

  • HalloWelt: LiStriRef - псевдонім вже ініціалізованого ListStringRef. Переданий List<string>об'єкт використовується для ініціалізації нового, тому refбув необхідним.

  • CiaoMondo: LiStriOutє псевдонімом ListStringOutі має бути ініціалізовано.

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


1

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

int x;    Foo(out x); // OK 
int y;    Foo(ref y); // Error

Параметри відбиття призначені для даних, які можуть бути змінені, вихідні параметри - це дані, які є додатковим висновком для функції (наприклад, int.TryParse), які вже використовують щось повернене значення.


1

Нижче я показав приклад, використовуючи і Ref, і Out . Тепер ви все будете звільнені від посилання та виходу.

У наведеному нижче прикладі, коли я коментую // myRefObj = new myClass {Name = "ref за межами дзвонив !!"}; рядок, отримає помилку із записом "Використання непризначеної локальної змінної" myRefObj "" , але такої помилки у виході немає .

Де використовувати Ref : коли ми викликаємо процедуру з параметром in і той самий параметр буде використаний для зберігання результатів цього процесу.

Де використовувати Out: коли ми викликаємо процедуру без параметра в параметрі, і той самий парам буде використаний для повернення значення з цього proc. Також відзначте результат

public partial class refAndOutUse : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        myClass myRefObj;
        myRefObj = new myClass { Name = "ref outside called!!  <br/>" };
        myRefFunction(ref myRefObj);
        Response.Write(myRefObj.Name); //ref inside function

        myClass myOutObj;
        myOutFunction(out myOutObj);
        Response.Write(myOutObj.Name); //out inside function
    }

    void myRefFunction(ref myClass refObj)
    {
        refObj.Name = "ref inside function <br/>";
        Response.Write(refObj.Name); //ref inside function
    }
    void myOutFunction(out myClass outObj)
    {
        outObj = new myClass { Name = "out inside function <br/>" }; 
        Response.Write(outObj.Name); //out inside function
    }
}

public class myClass
{
    public string Name { get; set; }
} 

1
 public static void Main(string[] args)
    {
        //int a=10;
        //change(ref a);
        //Console.WriteLine(a);
        // Console.Read();

        int b;
        change2(out b);
        Console.WriteLine(b);
        Console.Read();
    }
    // static void change(ref int a)
    //{
    //    a = 20;
    //}

     static void change2(out int b)
     {
         b = 20;
     }

ви можете перевірити цей код, він опише вам його повну різницю, коли ви використовуєте "ref" його означає, що ви вже ініціалізуєте цей int / string

але коли ви використовуєте "out", він працює в обох умовах, коли ви ініціалізуєте цей int / string чи ні, але ви повинні ініціалізувати цей int / string у цій функції


1

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

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

public class Example
{
 public static void Main() 
 {
 int val1 = 0; //must be initialized 
 int val2; //optional

 Example1(ref val1);
 Console.WriteLine(val1); 

 Example2(out val2);
 Console.WriteLine(val2); 
 }

 static void Example1(ref int value) 
 {
 value = 1;
 }
 static void Example2(out int value) 
 {
 value = 2; 
 }
}

/* Output     1     2     

Перегляньте і перевантажуйте метод перевантаження

І перегляд, і вихід не можна використовувати одночасно в перевантаженні методу. Тим не менш, ref і out трактуються по-різному під час виконання, але вони обробляються однаково під час компіляції (CLR не розмежовує два, в той час як він створював IL для ref та out).


0

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

З точки зору абонента, C # за багатьох обставин припускає, що коли виклик методу з outпараметром призведе до того, що передана змінна буде записана, не прочитавши її спочатку. Це припущення може бути невірним при виклику методів, написаних іншими мовами. Наприклад:

struct MyStruct
{
   ...
   myStruct(IDictionary<int, MyStruct> d)
   {
     d.TryGetValue(23, out this);
   }
}

Якщо myDictionaryідентифікується IDictionary<TKey,TValue>реалізація, написана мовою, відмінною від C #, хоч це MyStruct s = new MyStruct(myDictionary);виглядає як призначення, воно потенційно може залишитись sбез змін.

Зауважте, що конструктори, написані на VB.NET, на відміну від C #, не роблять припущень щодо того, чи будуть виправлені методи змінювати будь-які outпараметри, і очищати всі поля беззастережно. Незвичайна поведінка, на яку згадувалося вище, не відбуватиметься з кодом, записаним повністю в VB або повністю в C #, але може виникнути, коли код, написаний на C #, викликає метод, написаний у VB.NET.


0

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


-3

Майте на увазі, що опорний параметр, переданий всередині функції, безпосередньо працює.

Наприклад,

    public class MyClass
    {
        public string Name { get; set; }
    }

    public void Foo()
    {
        MyClass myObject = new MyClass();
        myObject.Name = "Dog";
        Bar(myObject);
        Console.WriteLine(myObject.Name); // Writes "Dog".
    }

    public void Bar(MyClass someObject)
    {
        MyClass myTempObject = new MyClass();
        myTempObject.Name = "Cat";
        someObject = myTempObject;
    }

Це напише Собака, а не Кіт. Отже, вам слід безпосередньо працювати над деякимОб'єктом.


6
Хоча тут все в значній мірі правда, це насправді не пояснює різницю між значенням за посиланням чи поза. У кращому випадку це наполовину пояснює різницю між еталонним та значенням / незмінними типами.
Конрад Фрікс

Якщо ви хочете, щоб у цьому коді було записано кішку, будь ласка, передайте цей об’єкт разом із клавішею 'ref', як це: публічна статична смуга void (ref MyClass someObject), Bar (ref myObject);
Даніель Ботеро

-4

Я, можливо, не дуже хороший у цьому, але, безумовно, рядки (навіть якщо вони технічно довідкові типи і живуть на купі) передаються за значенням, а не посиланням?

        string a = "Hello";

        string b = "goodbye";

        b = a; //attempt to make b point to a, won't work.

        a = "testing";

        Console.WriteLine(b); //this will produce "hello", NOT "testing"!!!!

Ось чому вам потрібна посилання, якщо ви хочете, щоб зміни існували поза межами функції, яка їх створює, ви не передаєте посилання в іншому випадку.

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

Я міг би тут абсолютно помилитися, я новачок.


5
Ласкаво просимо в Stack Overflow, Едвін. Наскільки я знаю, рядки передаються за посиланням, як і будь-який інший об'єкт. Вас можуть збентежити, оскільки рядки є незмінними об'єктами, тому не так очевидно, що вони передаються посиланням. Уявіть, що у рядку був метод, який називається, Capitalize()який змінює зміст рядка на великі літери. Якщо ви замінили рядок a = "testing";на a.Capitalize();, то ваш вихід буде "HELLO", а не "Hello". Однією з переваг непорушних типів є те, що ви можете обходити посилання та не турбуватися про зміну іншого значення коду.
Дон Кіркбі

2
Існує три основні типи семантики, яку може викрити тип: змінна референтна семантика, семантика змінного значення та незмінна семантика. Розглянемо змінні x і y типу T, у яких є поле або властивість m, і припустимо, що x скопійовано у y. Якщо T має еталонну семантику, зміни на xm будуть спостерігатися ym Якщо T має значення семантики, можна змінити xm, не впливаючи на ym. Якщо T має незмінна семантика, ні xm, ні ym ніколи не зміниться. Незмінна семантика може бути змодельована як об'єктами посилання, так і значеннями. Струни - це незмінні орієнтири.
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.