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

StackOverflow https://stackoverflow.com/questions/388464

Вопрос

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

public void myFunction(ref MyClass someClass)

а также

public void myFunction(out MyClass someClass)

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

Это было полезно?

Решение

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

Так что пока ref на два раза, out только вне.

Другие советы

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

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

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

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

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

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

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

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

Я думаю, что мы понимаем, как типы ценностей работают правильно? Типы значений (Int, Long, Struct и т. Д.). Когда вы отправляете их в функцию без команды REF, он копирует данные. Анкет Все, что вы делаете с этими данными в функции, только влияет на копию, а не на оригинал. Команда REF отправляет фактические данные, и любые изменения будут влиять на данные вне функции.

ОК, в запутанной части, ссылочные типы:

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

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

Когда ты новичок кто -то, созданы две части:

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

Теперь, когда вы отправляете кто -то в метод без ссылки копирует ссылка указатель, а не данные. Итак, теперь у вас есть:

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

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

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

Если вы обнаружите ссылку2 или укажите его на новые данные, это не повлияет на ссылку1, ни ссылку на данные1.

(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

Теперь, что происходит, когда вы отправляете кто -то ссылкой на метод? А Фактическая ссылка к кто -то отправляется на метод. Итак, теперь у вас есть только одна ссылка на данные:

(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

ref находится в а также вне.

Вы должны использовать out В предпочтениях, где бы ни было достаточно ваших требований.

вне:

В C#метод может вернуть только одно значение. Если вам нравится возвращать более одного значения, вы можете использовать ключевое слово Out. Модификатор выхода возвращается как возвращение за ссылкой. Самый простой ответ заключается в том, что ключевое слово «Out» используется для получения значения из метода.

  1. Вам не нужно инициализировать значение в вызовной функции.
  2. Вы должны присвоить значение в функции CALL, в противном случае компилятор сообщит об ошибке.

ref:

В C#, когда вы передаете тип значения, такой как int, float, Double и т. Д. В качестве аргумента параметра метода его передают по значению. Поэтому, если вы изменяете значение параметра, оно не влияет на аргумент в вызове метода. Но если вы отмечаете параметр с помощью ключевого слова «ref», он будет отражаться в фактической переменной.

  1. Вам необходимо инициализировать переменную, прежде чем вызывать функцию.
  2. Не обязательно назначать какое -либо значение параметру ссылки в методе. Если вы не измените ценность, что нужно пометить его как «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;
    }

Поскольку вы проходите в типе эталонного типа (класс), нет необходимости использовать 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 Если вы хотите изменить объект внутри вашего метода.

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

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

Для тех, кто учится на примере (как я) вот что Энтони Колесов говорит.

Я создал несколько минимальных примеров REF, OUT и других, чтобы проиллюстрировать этот момент. Я не освещаю лучшие практики, просто примеры, чтобы понять различия.

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

"Бейкер"

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

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

string s = "Able";

Тогда S - это ссылка на класс строки, который содержит текст «способный»! Другое назначение к той же переменной через

s = "Baker";

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

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

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

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

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

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


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

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

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

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();

      }
   }
}

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) Мы создаем метод вызова Main()

(2) Он создает объект списка (который является объектом типа ссылки) и хранит его в переменной myList.

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

Во время выполнения:

(3) Время выполнения выделяет память на стеке на #00, достаточно широко, чтобы хранить адрес ( #00 = myList, поскольку имена переменных на самом деле являются просто псевдонимом для местоположений памяти)

(4) Среда выполнения создает объект списка на куче в местоположении памяти #ff (все эти адреса, например, сакс)

(5) Среда выполнения затем сохранит начальный адрес #ff объекта на #00 (или, по словам, хранит ссылку на объект списка в указателе myList)

Вернемся к авторовке:

(6) Затем мы передаем объект списка как аргумент myParamList к вызываемому методу modifyMyList и назначить ему новый объект списка

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 этим значением (или с учетом исходного объекта списка и указал на новый объект списка в этом методе)

Адрес в № 00 не изменен и сохраняет ссылку на #FF (или оригинал myList Указатель не нарушен).


А рефери Ключевое слово - это директива компилятора, чтобы пропустить генерацию кода времени выполнения для (8) и (9), что означает, что не будет распределения кучи для параметров метода. Он будет использовать оригинальный указатель #00 для работы на объекте на #FF. Если исходный указатель не инициализирован, время выполнения остановит жалобу, он не может продолжаться, поскольку переменная не инициализирована

А вне Ключевое слово - это директива компилятора, которая в значительной степени такая же, как REF с небольшой модификацией в (9) и (10). Компилятор ожидает, что аргумент будет неонициализирован и будет продолжаться с (8), (4) и (5) создать объект на куче и хранить свой начальный адрес в переменной аргумента. Никакая неинициализированная ошибка не будет выброшена, и любая предыдущая сохраненная ссылка будет потеряна.

Они почти одинаковы - единственное отличие состоит в том, что переменная, которую вы передаете в виде параметра OUT, не нужно инициализировать, и метод, использующий параметр Ref, должен установить ее на что -то.

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

Параметры REF предназначены для данных, которые могут быть изменены, параметры OUT предназначены для данных, которые являются дополнительным выводом для функции (например, Int.t.trarse), которые уже используют возвратное значение для чего -то.

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

В ниже упомянутого примера, когда я комментирую // myRefobj = new myclass {name = "ref снаружи под названием !!"};строка, получит ошибку «Использование незнашивающейся локальной переменной« myrefobj »», но такой ошибки нет в вне.

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

Где использовать: Когда мы вызываем процедуру без параметра, а тот же параметр будет использоваться для возврата значения из этого 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; }
} 
 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 в этой функции

Ссылка: Ключевое слово Ref используется для передачи аргумента в качестве ссылки. Это означает, что когда значение этого параметра изменяется в методе, оно отражается в вызовом методе. Аргумент, который передается с использованием ключевого слова REF, должен быть инициализирован в методе вызова, прежде чем оно будет передано в вызываемый метод.

OUT: Ключевое слово Out также используется для передачи такого аргумента, как Key Whord Ref, но аргумент может быть передан, не присваивая ему какое -либо значение. Аргумент, который передается с использованием ключевого слова OUT, должен быть инициализирован в методе CALL, прежде чем он вернется к методу вызова.

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 не могут быть использованы при перегрузке метода одновременно. Тем не менее, Ref и Out обращаются по-разному во время выполнения, но они обрабатываются одинаково во время компиляции (CLR не отличается между ними, пока он создал IL для REF и OUT).

С точки зрения метода, который получает параметр, разница между 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.

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

Помимо разрешения переназначить чужую переменную в другой экземпляр класса, вернуть несколько значений и т. Д. с использованием 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 x = SpendHoursMakingMeAPerfectSomeClass();
//now give it to the library
PoorlyNamedMethod(out x);

Ну, это была пустая трата времени, уделяя все эти часы, чтобы сделать этот идеальный класс. Это определенно будет отброшено и заменен плохомедметодом

Помните, что эталонный параметр, который передается внутри функции, работает напрямую.

Например,

    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;
    }

Это напишет собаку, а не кошку. Следовательно, вам следует напрямую работать над каким -то.

Возможно, я не так хорош в этом, но, несомненно, строки (даже если они технически являются эталонными типами и живут на куче) передаются по значению, а не ссылкам?

        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"!!!!

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

Насколько мне известно, вам нужен только ссылка только для структур/типов значений и самой строки, так как строка является эталонным типом, который притворяется, что это является, но не является типом значения.

Я мог бы быть совершенно неправ, хотя, я новичок.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top