В чем разница между ключевыми словами ref и out?


892

Я создаю функцию, в которой мне нужно передать объект, чтобы он мог быть изменен функцией. В чем разница между:

public void myFunction(ref MyClass someClass)

а также

public void myFunction(out MyClass someClass)

Что я должен использовать и почему?


69
Вы: Мне нужно передать объект, чтобы его можно было изменить. Похоже, MyClassэто будет classтип, то есть ссылочный тип. В этом случае передаваемый вами объект может быть изменен myFunctionдаже без ключевого слова ref/ out. myFunctionполучит новую ссылку, которая указывает на тот же объект, и он может изменять тот же объект столько, сколько он хочет. Различие ref, которое могло бы иметь ключевое слово, заключалось бы в том, что он myFunctionполучал одну и ту же ссылку на один и тот же объект. Это было бы важно, только если myFunctionбы изменить ссылку, чтобы указать на другой объект.
Джеппе Стиг Нильсен

3
Я озадачен количеством запутанных ответов здесь, когда @ AnthonyKolesov's совершенно совершенен.
о0 '.

Объявление метода out полезно, когда вы хотите, чтобы метод возвращал несколько значений. Один аргумент может быть назначен на ноль. Это позволяет методам возвращать значения по желанию.
Евграф Андреевич Живаго

Здесь объяснено с помощью примера более понятным :) dotnet-tricks.com/Tutorial/csharp/…
Prageeth godage

2
Технически, комментарий @ JeppeStigNielsen является (единственным) правильным ответом на фактический вопрос ОП. Чтобы передать объект в метод, чтобы метод мог модифицировать объект , просто передайте (ссылку на) объект в метод по значению. Изменение объекта в методе с помощью аргумента объекта изменяет исходный объект , даже если метод содержит свою собственную отдельную переменную (которая ссылается на тот же объект).
Дэвид Р. Триббл

Ответы:


1162

refсообщает компилятору, что объект инициализируется перед входом в функцию, а outкомпилятору сообщает, что объект будет инициализирован внутри функции.

Так что пока refесть два пути, outэто только для внешнего.


270
Еще одна интересная особенность out - это то, что функция должна присваивать параметр out. Не разрешается оставлять это без назначения.
Даниэль Эрвикер

7
'ref' применим только к типу значения? Поскольку ссылочный тип всегда передается по ссылке.
неисправен

3
Да. Типы значений, включая структуры
Руна Гримстад

17
@faulty: Нет, ссылка относится не только к типам значений. ref / out похожи на указатели в C / C ++, они имеют дело с расположением в памяти объекта (косвенно в C #) вместо прямого объекта.
Чет

52
@faulty: нелогично, ссылочные типы всегда передаются по значению в C #, если вы не используете спецификатор ref. Если вы установите myval = somenewval, эффект будет только в этой области действия функции. Ключевое слово ref позволит вам изменить myval, указав somenewval.
JasonTrue

535

В refмодификатор означает , что:

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

В outмодификатор означает , что:

  1. Значение не установлено и не может быть прочитано методом, пока оно не установлено.
  2. Метод должен установить его перед возвратом.

30
Этот ответ наиболее четко и кратко объясняет ограничения, которые накладывает компилятор при использовании ключевого слова out, а не ключевого слова ref.
Ученик доктора Вилли

5
Из MSDN: параметр ref должен быть инициализирован перед использованием, в то время как параметр out не должен быть явно инициализирован перед передачей, а любое предыдущее значение игнорируется.
Шива Кумар

1
С out, можно ли вообще прочитать его внутри метода, до того, как он был установлен этим методом, если он был инициализирован до вызова метода? Я имею в виду, может ли вызываемый метод прочитать, что вызывающий метод передал ему в качестве аргумента?
Panzercrisis

3
Panzercrisis, для "out", вызываемый метод может прочитать, если он уже установлен. но он должен установить его снова.
Роберт Джебакумар

146

Допустим, Дом появляется в кабинете Питера о записке о докладах TPS.

Если бы Дом был аргументом в пользу ссылки, у него была бы печатная копия записки.

Если бы Дом был спорным аргументом, он заставил бы Питера напечатать новую копию памятки, которую он мог взять с собой.


54
Реф Дом написал бы отчет карандашом, чтобы Питер мог его изменить
Дибстер

6
@ Дибстер, ты знаешь, эта метафора ничего не сделала для тебя, почему ты так мучаешь это? ;)
Майкл Блэкберн

21
занимательный, но обучающий, stackoverflow нуждается в большем количестве подобных сообщений
Фрэнк Визажо

2
На всякий случай, если кто-то посчитает этот ответ лишь смешным, посмотрите фильм «Офисное пространство».
displayName

и босс Dom и Peters будет стоять здесь за Dom (в качестве аргумента), заставляя обоих работать над его распечатыванием заново, пока Питер не отдаст распечатку Domd
Патрик Артнер

57

Я собираюсь попробовать свои силы в объяснении:

Я думаю, мы понимаем, как типы значений работают правильно? Типы значений (int, long, struct и т. Д.). Когда вы отправляете их в функцию без команды ref, она копирует данные . Все, что вы делаете с этими данными в функции, влияет только на копию, а не на оригинал. Команда ref отправляет ФАКТИЧЕСКИЕ данные, и любые изменения будут влиять на данные вне функции.

Хорошо, к запутанной части, ссылочные типы:

Давайте создадим ссылочный тип:

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

Когда вы создаете новый объект , создаются две части:

  1. Блок памяти, который содержит данные для некоторого объекта .
  2. Ссылка (указатель) на этот блок данных.

Теперь, когда вы отправляете некоторый объект в метод без ссылки, он КОПИРУЕТ указатель ссылки , а НЕ данные. Итак, теперь у вас есть это:

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

Две ссылки, указывающие на один и тот же объект. Если вы изменяете свойство в каком-либо объекте, используя reference2, это повлияет на те же данные, на которые указывает reference1.

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

Если вы обнулите reference2 или укажете на новые данные, это не повлияет на reference1 или на reference1.

(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 отправляется к методу. Теперь у вас есть только одна ссылка на данные:

(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в предпочтениях там, где этого достаточно для ваших требований.


не совсем, как принятый ответ ref, если направленный и бесполезный, игнорируя значения-типы, если не передан обратно.
Кенни

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

(Кстати, я знаком с типами значений, ссылочными типами, передачей по ссылке, передачей по значению, COM и C ++, если вам будет полезно сделать ссылку на эти концепции в вашем разъяснении)
Рубен Бартелинк

1
Ссылки на объекты передаются по значению (кроме случаев использования ключевого слова «ref» или «out»). Думайте об объектах как об идентификационных номерах. Если переменная класса содержит «Object # 1943» и кто-то передает эту переменную по значению в подпрограмму, эта подпрограмма может вносить изменения в Object # 1943, но не может заставить переменную указывать на что-либо, кроме «Object # 1943». Если переменная была передана по ссылке, подпрограмма могла бы удерживать точку переменной «Object # 5441».
суперкат

1
@supercat: мне нравится ваше объяснение ref vs val (и эта последующая анаология). Я думаю, что Кенни на самом деле не нужно ничего из того, что ему объяснили, (относительно) сбивает с толку, как и его комментарии. Хотелось бы, чтобы мы все просто удалили эти проклятые комментарии, хотя они просто смущают всех. Коренная причина всей этой чепухи, кажется, состоит в том, что Кенни неправильно прочитал мой ответ и еще не указал на одно слово, которое должно быть добавлено / удалено / заменено. Никто из нас троих не узнал ничего из дискуссии, которую мы уже не знали, и у другого ответа есть смешное количество голосов.
Рубен Бартелинк

18

вне:

В C # метод может возвращать только одно значение. Если вы хотите вернуть более одного значения, вы можете использовать ключевое слово out. Модификатор 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
Это неправильно - попробуйте следующее: добавить someObject = nullв Barконец выполнить. Ваш код будет работать нормально, так как только Barссылка на экземпляр была обнулена. Теперь измените Barна Bar(ref MyClass someObject)и выполните снова - вы получите, NullReferenceExceptionпотому что Fooссылка на экземпляр тоже была обнулена.
Кит

8

refи outвести себя аналогично, за исключением следующих различий.

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


6

«Бейкер»

Это потому, что первый изменяет вашу строковую ссылку, чтобы указать на «Бейкер». Изменение ссылки возможно, потому что вы передали ее через ключевое слово ref (=> ссылка на ссылку на строку). Второй вызов получает копию ссылки на строку.

Поначалу строка выглядит какой-то особенной. Но строка это просто ссылочный класс, и если вы определите

string s = "Able";

затем s является ссылкой на строковый класс, который содержит текст «Able»! Еще одно присвоение той же переменной через

s = "Baker";

не изменяет исходную строку, а просто создает новый экземпляр и позволяет s указать на этот экземпляр!

Вы можете попробовать это с помощью следующего небольшого примера кода:

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

Что вы ожидаете? То, что вы получите, все еще «Able», потому что вы просто устанавливаете ссылку в s на другой экземпляр, в то время как s2 указывает на исходный экземпляр.

РЕДАКТИРОВАТЬ: строка также является неизменной, что означает, что просто не существует метода или свойства, которое изменяет существующий экземпляр строки (вы можете попытаться найти его в документации, но вы не найдете ничего :-)). Все методы работы со строками возвращают новый экземпляр строки! (Вот почему вы часто получаете лучшую производительность при использовании класса StringBuilder)


1
Точно. Поэтому не совсем верно говорить «Поскольку вы передаете ссылочный тип (класс), нет необходимости использовать ref».
Пол Митчелл

Теоретически это правильно сказать, потому что он написал «чтобы его можно было изменить», что невозможно в строках. Но из-за неизменяемых объектов «ref» и «out» очень полезны также для ссылочных типов! (.Net содержит много неизменных классов!)
мммммммм

Да, ты прав. Я не думал о неизменных объектах, таких как строки, потому что большинство объектов являются изменяемыми.
Albic

1
Ну, это загадочный ответ, чтобы увидеть в LQP, чтобы быть уверенным; в этом нет ничего плохого, за исключением того, что он кажется длинным и подробным ответом на другой комментарий (поскольку в первоначальном вопросе ни разу не упоминается Able и Baker), как если бы это был форум. Я предполагаю, что это еще не решено еще когда-то.
Натан Тагги

6

ref означает, что значение в параметре ref уже установлено, метод может прочитать и изменить его. Использование ключевого слова ref - это то же самое, что сказать, что вызывающая сторона отвечает за инициализацию значения параметра.


out сообщает компилятору, что за инициализацию объекта отвечает функция, которую функция должна назначить параметру out. Не разрешается оставлять это без назначения.


5

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

Следующий пример иллюстрирует это:

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 ++.

Для ref аргумент должен быть объявлен и инициализирован.

Для, аргумент должен быть объявлен, но может или не может быть инициализирован

        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) Runtime создает список объектов в куче в ячейке памяти #FF (все эти адреса, например, sakes)

(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) Runtime запускает процедуру вызова для вызываемого метода и, как часть его, проверяет тип параметров.

(8) После нахождения ссылочного типа он выделяет память в стеке в # 04 для наложения псевдонима переменной параметра myParamList.

(9) Затем он также сохраняет в нем значение #FF.

(10) Runtime создает объект списка в куче в ячейке памяти # 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объект out для совершенно нового объекта и хотите, чтобы вызывающий метод увидел ваши изменения

    • Запись 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 на что-то другое»

Конечно, они должны это делать, например, при синтаксическом анализе типов значений, потому что если бы методу parse не было разрешено поменять тип значения на что-то другое, это не сработало бы очень хорошо ... Но представьте, что в некоторых есть какой-то фиктивный метод библиотека, которую вы создаете:

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

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


1

Ниже я показал пример использования как Ref, так и out . Теперь вы все будете очищены от реф и вне.

В приведенном ниже примере, когда я комментирую // myRefObj = new myClass {Name = "ref outside named !!"}; line, получит сообщение об ошибке «Использование неназначенной локальной переменной myRefObj» , но в out такой ошибки нет .

Где использовать Ref : когда мы вызываем процедуру с параметром in, и этот же параметр будет использоваться для хранения выходных данных этого процесса.

Где использовать Out: когда мы вызываем процедуру без параметра in и тот же параметр будет использоваться для возврата значения из этого процесса. Также обратите внимание на вывод

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: ключевое слово 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 нельзя использовать одновременно в перегрузке метода. Тем не менее, 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

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


-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
Хотя все здесь в значительной степени верно, на самом деле это не объясняет разницу между значением по ссылке или вне. В лучшем случае он наполовину объясняет разницу между ссылочным и неизменяемым типом.
Конрад Фрикс

Если вы хотите, чтобы этот код писал cat, передайте этот объект вместе с ключом ref следующим образом: public static void Bar (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, если вы хотите, чтобы изменения существовали вне области действия функции, делающей их, иначе вы не передадите ссылку.

Насколько я знаю, вам нужен только 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 никогда не изменятся. Неизменная семантика может быть смоделирована либо объектами ссылки, либо объектами значений. Строки являются неизменяемыми ссылочными объектами.
суперкат
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.