Я создаю функцию, в которую мне нужно передать объект, чтобы он мог быть изменен функцией. В чем разница между:
public void myFunction(ref MyClass someClass)
а также
public void myFunction(out MyClass someClass)
Что мне использовать и почему?
Меня озадачивает количество запутанных ответов здесь, когда @ AnthonyKolesov совершенно идеален.
Объявление метода out полезно, когда вы хотите, чтобы метод возвращал несколько значений. Один аргумент можно присвоить null. Это позволяет методам опционально возвращать значения.
Вот объяснил на примере Это более понятно :) dotnet-tricks.com/Tutorial/csharp/…
Комментарий @ JeppeStigNielsen технически является (единственным) правильным ответом на фактический вопрос OP. Чтобы передать объект в метод таким образом, чтобы метод может изменять объект, просто передайте объект (ссылку на) в метод по значению. Изменение объекта в методе с помощью аргумента объекта изменяет исходный объект, даже если метод содержит свою собственную отдельную переменную (которая ссылается на тот же объект).





ref находится в а также out.
Вы должны использовать out, если этого достаточно для ваших требований.
не совсем так, как принятый ответ ref, если направлен и бесполезно игнорировать типы значений, если не передается обратно.
@kenny: Не могли бы вы немного уточнить, например, какие слова вы бы изменили, чтобы сохранить дух ответа, но убрать неточность, которую вы воспринимаете? Мой ответ не является сумасшедшим предположением новичка, но поспешность (краткость, опечатки) в вашем комментарии, кажется, предполагает, что это так. Цель состоит в том, чтобы с помощью наименьшего количества слов дать представление о различиях.
(Кстати, я знаком с типами значений, ссылочными типами, передачей по ссылке, передачей по значению, COM и C++, если вам будет полезно сделать ссылку на эти концепции в своем пояснении)
Я не возражаю, но только мелкая нить с «ref» только для ввода. У меня 2 очка. 1 объекты всегда передаются по ссылке. И второй момент заключается в том, что вы добавляете ссылку только в том случае, если вас волнует, что происходит внутри метода или у него есть побочные эффекты для вызывающего. Я думаю, что, может быть, я неправильно истолковал ваш ответ в контексте вопроса, я прочитал его, поскольку «ref» означает «in», а «out» - «out».
Ссылки на объекты передаются по значению (кроме случаев использования ключевого слова «ref» или «out»). Думайте об объектах как об идентификационных номерах. Если переменная класса содержит «Объект № 1943» и кто-то передает эту переменную по значению в подпрограмму, эта подпрограмма может вносить изменения в объект № 1943, но не может указывать на что-либо иное, кроме «Объект № 1943». Если переменная была передана по ссылке, процедура могла бы заставить точку переменной удерживать "Object # 5441".
@supercat: Если вы согласны (не видите +1) и хотите объяснить @ kenny, вам нужно @ его войти. Если вы хотите объяснить мне разницу между ссылками и значениями, вы только что это сделали ... Если вы считаете, что этот ответ неверен и его нужно переписать на основе вашего комментария, не стесняйтесь редактировать и / или добавлять свой ответ.
@RubenBartelink: Спасибо за предупреждение; Я хотел известить Кенни. Хотелось бы, чтобы описание «ID объекта» использовалось шире; Я думаю, это проясняет ситуацию. Если рабочему кузовного цеха передают лист бумаги с номером лицензии и просят покрасить эту машину в синий цвет, можно ожидать, что он найдет машину, права которой совпадают с лицензией на бумаге, и раскрасит ее, оставив номер только на бумаге. . От него не ожидали, что он раскрасит бумагу синим цветом или перекрасит ее синими буквами.
@supercat: Мне нравится ваше объяснение ref vs val (и этой последующей анаологии). Я думаю, что Кенни на самом деле не нужно объяснять ему что-либо из этого, хотя (относительно) запутанные его комментарии были. Я действительно хотел бы, чтобы мы все могли просто удалить эти проклятые комментарии, поскольку они просто сбивают всех с толку. Основная причина всей этой чепухи, по-видимому, в том, что Кенни неправильно прочитал мой ответ и еще не указал ни одного слова, которое следует добавить / удалить / заменить. Никто из нас троих ничего не извлек из обсуждения, о котором мы еще не знали, а другой ответ получил смехотворное количество голосов.
ref сообщает компилятору, что объект инициализируется перед входом в функцию, а out сообщает компилятору, что объект будет инициализирован внутри функции.
Таким образом, в то время как ref является двусторонним, out - только выходящим.
Еще одна интересная особенность out - это то, что функция должна назначать параметр out. Нельзя оставлять его неназначенным.
"ref" применимо только к типу значения? Поскольку ссылочный тип всегда передается по ссылке.
Да. Типы значений, включая структуры
@faulty: Нет, ссылка применима не только к типам значений. ref / out похожи на указатели в C / C++, они имеют дело с местом в памяти объекта (косвенно в C#) вместо прямого объекта.
@faulty: Как ни странно, ссылочные типы всегда передаются по значению в C#, если вы не используете спецификатор ref. Если вы установите myval = somenewval, эффект будет только в области этой функции. Ключевое слово ref позволит вам изменить myval, чтобы он указывал на какое-то новое значение.
@DanielEarwicker: для функций Реализовано на C# компилятор будет настаивать на том, чтобы параметры out были записаны во всех неисключительных путях кода; однако это не обязательно для функций, в том числе виртуальная функция отменяет, реализованных на других языках. Например, вполне возможно, что реализация IDictionary<TK,TV>.TryGetValue(TK, out TV) оставит свой параметр out неизменным, если значение не будет найдено.
@faulty Вы действительно должны проверить еще один вопрос, описывающий ref, используемый со ссылочными типами ;-).
Есть трюк, как перейти к неинициализированному объекту ref без каких-либо ошибок времени компиляции, см. Мой отвечать
И ref, и out нельзя использовать при перегрузке метода одновременно. Однако во время выполнения ref и out обрабатываются по-разному, но во время компиляции они обрабатываются одинаково. CLR не делает различий между ними в IL.
@BharathkumarV Разве ваш комментарий не обратный? ref и out обрабатываются по-разному во время компиляции, но обрабатываются одинаково во время выполнения.
Поскольку вы передаете ссылочный тип (класс), нет необходимости использовать 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, если вы хотите изменить объект внутри своего метода.
Это работает, только если новый объект не создается и не возвращается. При создании нового объекта ссылка на старый объект будет потеряна.
Это неправильно - попробуйте следующее: добавить someObject = null в Bar завершить выполнение. Ваш код будет работать нормально, поскольку только ссылка Bar на экземпляр была обнулена. Теперь измените Bar на Bar(ref MyClass someObject) и выполните снова - вы получите NullReferenceException, потому что ссылка Foo на экземпляр тоже была обнулена.
Модификатор ref означает, что:
Модификатор out означает, что:
Этот ответ наиболее четко и кратко объясняет ограничения, которые компилятор налагает при использовании ключевого слова out, а не ключевого слова ref.
Из MSDN: параметр ref должен быть инициализирован перед использованием, в то время как параметр out не должен быть явно инициализирован перед передачей, и любое предыдущее значение игнорируется.
С out можно ли вообще прочитать его в методе до того, как он был установлен этим методом, если он был инициализирован до вызова метода? Я имею в виду, может ли вызываемый метод читать то, что вызывающий метод передал ему в качестве аргумента?
Panzercrisis, для "out" вызываемый метод может читать, если он уже установлен. но он должен установить его снова.
"Бейкер"
Это потому, что первый изменяет вашу ссылку на строку, чтобы указать на «Бейкер». Изменение ссылки возможно, потому что вы передали ее с помощью ключевого слова ref (=> ссылка на ссылку на строку). Второй вызов получает копию ссылки на строку.
Строка поначалу выглядит особенной. Но строка - это просто ссылочный класс, и если вы определите
string s = "Able";
тогда s - это ссылка на строковый класс, содержащий текст «Able»! Другое присвоение той же переменной через
s = "Baker";
не изменяет исходную строку, а просто создает новый экземпляр, и пусть s указывает на этот экземпляр!
Вы можете попробовать это с помощью следующего небольшого примера кода:
string s = "Able";
string s2 = s;
s = "Baker";
Console.WriteLine(s2);
Что вы ожидаете? Вы получите "Able", потому что вы просто установили ссылку в s на другой экземпляр, в то время как s2 указывает на исходный экземпляр.
Обновлено: string также является неизменяемым, что означает, что просто нет метода или свойства, которое изменяет существующий экземпляр строки (вы можете попытаться найти его в документации, но вы не найдете ни одного :-)). Все методы обработки строк возвращают новый экземпляр строки! (Вот почему вы часто получаете лучшую производительность при использовании класса StringBuilder)
Точно. Поэтому не совсем верно сказать: «Поскольку вы передаете ссылочный тип (класс), нет необходимости использовать ref».
Теоретически это правильно, потому что он написал «так, чтобы это можно было изменить», что невозможно для струнных. Но из-за неизменных объектов «ref» и «out» очень полезны также для ссылочных типов! (.Net содержит множество неизменяемых классов!)
Да, ты прав. Я не думал о неизменяемых объектах, таких как строки, потому что большинство объектов являются изменяемыми.
Что ж, это, конечно, загадочный ответ, который стоит увидеть в LQP; в этом нет ничего плохого, за исключением того, что это кажется длинным и подробным ответом на другой комментарий (поскольку исходный вопрос не упоминает Эйбла и Бейкера ни в одной из его редакций), как если бы это был форум. Я предполагаю, что это еще не было решено еще тогда, когда.
Допустим, Дом появляется в кабинке Питера по поводу записки об отчетах TPS.
Если бы Дом был аргументом ref, у него была бы распечатанная копия служебной записки.
Если бы Дом был неприкрытым аргументом, он заставил бы Питера распечатать новую копию служебной записки, чтобы тот взял с собой.
ref Дом написал бы отчет карандашом, чтобы Питер мог его изменить.
@Deebster, знаешь, эта метафора ничего тебе не сделала, почему ты должен ее так мучить? ;)
занимательный, но обучающий, stackoverflow нужно больше таких сообщений
На тот случай, если кто-то сочтет этот ответ полусмешным, посмотрите фильм «Офисное пространство».
а Дом и босс Питерса стояли бы за Домом (в качестве аргумента), заставляя обоих работать над распечаткой заново, пока Питер не передал Домду распечатку.
Отличное объяснение. Это очень полезно для студентов, которым сложно понять основные концепции C#. Так держать :-)
Помните, что ссылочный параметр, который передается внутри функции, обрабатывается напрямую.
Например,
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;
}
Будет написано «Собака», а не «Кошка». Следовательно, вы должны напрямую работать с someObject.
Хотя здесь все в значительной степени верно, на самом деле это не объясняет разницу между значением по ссылке или выходом. В лучшем случае это наполовину объясняет разницу между ссылочными и значениями / неизменяемыми типами.
Если вы хотите, чтобы этот код писал cat, передайте этот объект вместе с ключом ref следующим образом: public static void Bar (ref MyClass someObject), Bar (ref myObject);
Расширение собаки, пример кота. Второй метод с 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;
}
Попробую объяснить:
Думаю, мы правильно понимаем, как работают типы значений? Типы значений (int, long, struct и т. д.). Когда вы отправляете их в функцию без команды ref, она КОПИРУЕТ данные. Все, что вы делаете с этими данными в функции, влияет только на копию, а не на оригинал. Команда ref отправляет ФАКТИЧЕСКИЕ данные, и любые изменения повлияют на данные вне функции.
Хорошо, перейдем к запутанной части, ссылочные типы:
Давайте создадим ссылочный тип:
List<string> someobject = new List<string>()
Когда вы обновляете какой-то объект, создаются две части:
Теперь, когда вы отправляете какой-то объект в метод без ссылки, он КОПИРУЕТ указатель Справка, а НЕ данные. Итак, теперь у вас есть это:
(outside method) reference1 => someobject
(inside method) reference2 => someobject
Две ссылки, указывающие на один и тот же объект. Если вы измените свойство в какой-то объект, используя ссылку2, это повлияет на те же данные, на которые указывает ссылка1.
(inside method) reference2.Add("SomeString");
(outside method) reference1[0] == "SomeString" //this is true
Если вы обнуляете reference2 или указываете на новые данные, это не повлияет ни на reference1, ни на ссылку data1, на которую указывает.
(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) есть только одна ссылка на данные, но для них два псевдонима. Верно?
Проголосовали за четкое объяснение. Но я думаю, что это не отвечает на вопрос, так как не объясняет разницу между параметрами ref и out.
Удивительный. можете объяснить то же самое, что и для ключевого слова out?
Возможно, я не настолько хорош в этом, но, конечно, строки (даже если они технически являются ссылочными типами и находятся в куче) передаются по значению, а не по ссылке?
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 только для типов структур / значений и самой строки, поскольку строка является ссылочным типом, который притворяется, но не является типом значения.
Я могу ошибаться здесь, я новичок.
Добро пожаловать в Stack Overflow, Эдвин. Строки передаются по ссылке, как и любой другой объект, насколько мне известно. Вы можете быть сбиты с толку, потому что строки являются неизменяемыми объектами, поэтому не так очевидно, что они передаются по ссылке. Представьте, что у этой строки есть метод под названием Capitalize(), который изменяет содержимое строки на заглавные буквы. Если вы затем замените свою строку a = "testing"; на a.Capitalize();, то вы получите «HELLO», а не «Hello». Одним из преимуществ неизменяемых типов является то, что вы можете передавать ссылки и не беспокоиться о том, что другой код изменит значение.
Существует три основных типа семантики, которые может предоставлять тип: семантика изменяемых ссылок, семантика изменяемых значений и неизменяемая семантика. Рассмотрим переменные x и y типа T, у которого есть поле или свойство m, и предположим, что x скопирован в y. Если T имеет ссылочную семантику, изменения x.m будут наблюдаться y.m. Если T имеет семантику значений, можно изменить x.m, не влияя на y.m. Если T имеет неизменяемую семантику, ни x.m, ни y.m никогда не изменятся. Неизменяемая семантика может моделироваться объектами ссылки или значения. Строки - это неизменяемые ссылочные объекты.
Они почти одинаковы - единственное отличие состоит в том, что переменную, которую вы передаете в качестве параметра out, не нужно инициализировать, а метод, использующий параметр ref, должен что-то установить.
int x; Foo(out x); // OK
int y; Foo(ref y); // Error
Параметры Ref предназначены для данных, которые могут быть изменены, параметры out - для данных, которые являются дополнительным выходом для функции (например, int.TryParse), которые уже используют возвращаемое значение для чего-то.
Ниже я показал пример с использованием как Ссылка, так и вне. Теперь у вас все будет ясно по поводу ref и out.
В приведенном ниже примере, когда я комментирую // myRefObj = new myClass {Name = "ref external called !!"}; строка, получит сообщение об ошибке «Использование неназначенной локальной переменной myRefObj» », но в вне такой ошибки нет.
Где использовать 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; }
}
ref и out ведут себя аналогично, за исключением следующих различий.
ref необходимо инициализировать. Переменная out может использоваться без присваиванияout должен рассматриваться функцией, которая его использует, как неназначенное значение. Итак, мы можем использовать инициализированный параметр 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();
}
}
}
ссылка: Ссылочный параметр - это ссылка на ячейку памяти переменной. Когда вы передаете параметры по ссылке, в отличие от параметров значений, для этих параметров не создается новое место хранения. Ссылочные параметры представляют собой ту же ячейку памяти, что и фактические параметры, которые передаются методу.
В 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();
}
}
}
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 / строку
но когда вы используете "вне" он работает в обоих условиях, если вы инициализируете этот int / string или нет но вы должны инициализировать эту int / строку в этой функции
ссылка означает, что значение в параметре ref уже установлено, метод может читать и изменять его. Использование ключевого слова 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.
ref и out работают так же, как передача по ссылкам и передача по указателям, как в C++.
Для ref аргумент должен быть объявлен и инициализирован.
Для out аргумент должен быть объявлен, но может быть инициализирован или не инициализирован.
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);
Вы можете объявить переменную встроенной: out double Half_nbr.
Ссылка: Ключевое слово 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 нельзя использовать при перегрузке метода одновременно. Однако во время выполнения ref и out обрабатываются по-разному, но во время компиляции они обрабатываются одинаково (CLR не различает их, пока создает IL для ref и out).
Для тех, кто учится на примере (например, я), вот что такое Антоний Колесов говорит.
Я создал несколько минимальных примеров ref, out и других, чтобы проиллюстрировать суть дела. Я не останавливаюсь на передовых методах, просто на примерах, чтобы понять различия.
В C# метод может возвращать только одно значение. Если вы хотите вернуть более одного значения, вы можете использовать ключевое слово out. Модификатор out возвращается как возврат по ссылке. Самый простой ответ - ключевое слово «out» используется для получения значения из метода.
В C#, когда вы передаете тип значения, такой как int, float, double и т. д., В качестве аргумента параметра метода, он передается по значению. Следовательно, если вы измените значение параметра, это не повлияет на аргумент в вызове метода. Но если вы отметите параметр ключевым словом «ref», он отразится в фактической переменной.
«В C# метод может возвращать только одно значение. Если вы хотите возвращать более одного значения, вы можете использовать ключевое слово out». Мы также можем использовать «ref» для возврата значения. Значит, мы можем использовать как ref, так и out, если хотим вернуть несколько значений из метода?
В C# 7 вы можете возвращать несколько значений с помощью ValueTuples.
Если вы хотите передать свой параметр в качестве ссылки, вам следует инициализировать его перед передачей параметра функции, иначе компилятор сам покажет ошибку. Но в случае параметра out вам не нужно инициализировать параметр объекта перед его передачей в Вы можете инициализировать объект в самом вызывающем методе.
Время создания:
(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. Если исходный указатель не инициализирован, среда выполнения перестанет жаловаться на невозможность продолжения работы, поскольку переменная не инициализирована.
Ключевое слово вне - это директива компилятора, которая в значительной степени совпадает с ref с небольшой модификацией в (9) и (10). Компилятор ожидает, что аргумент не инициализирован, и продолжит с (8), (4) и (5), чтобы создать объект в куче и сохранить его начальный адрес в переменной аргумента. Никакая неинициализированная ошибка не будет выдана, и любая предыдущая сохраненная ссылка будет потеряна.
Помимо того, что вы можете переназначить чужую переменную другому экземпляру класса, вернуть несколько значений и т. д., использование 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 приводит к утверждению: «Передать переменную-заполнитель моему методу. Не имеет значения, имеет ли она значение или нет; компилятор заставит меня присвоить ему новое значение. Я абсолютно гарантирую, что объект, на который указывает ваша переменная до того, как вы вызвали мой метод, будут будет другим к тому времени, когда я закончу
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.
Чтобы проиллюстрировать множество прекрасных объяснений, я разработал следующее консольное приложение:
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 ведут себя одинаково.
Для ищущих лаконичного ответа.
Both
refandoutkeywords are used to pass-by-reference.A variable of
refkeyword must have a value or must refer to an object ornullbefore its passing.Unlike
ref, a variable ofoutkeyword must have a value or must refer to an object ornullafter its passing as well as no need to have a value or refer to an object before passing.
Вы: Мне нужно передать объект, чтобы его можно было изменить Похоже, что
MyClassбудет типомclass, то есть ссылочным типом. В этом случае передаваемый вами объект может быть измененmyFunctionдаже без ключевого словаref/out.myFunctionполучит ссылку новый, указывающую на объект одно и тоже, и может изменять этот же объект сколько угодно. Ключевое словоrefможет отличаться тем, чтоmyFunctionполучил ссылку одно и тоже на тот же объект. Это было бы важно, только если быmyFunctionизменил ссылку на объект Другой.