Перейти к содержимому

Что такое out в c

  • автор:

Чем out отличается от ref?

Разница в том, что out — это выходной параметр, а ref — входно-выходной.

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

(Таким образом, ref -параметр немного напоминает инициализированную локальную переменную, а out -параметр — неинициализированную.)

private void func1(out string value) < Console.WriteLine(value); // нельзя, value не инициализировано if (false) return; // нельзя, забыли установить значение value value = "Hello World!"; >string s1; func1(out s1); 
private void func2(ref string value) < Console.WriteLine(value); // можно if (false) return; // не проблема, у value остаётся старое значение value = "Hello World!"; >string s2; func2(ref s2); // нельзя, функция имеет право использовать значение, // значит, оно должно быть инициализировано сначала 

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

На уровне CLR для out — и ref -параметров используется один и тот же механизм, но это незначительная техническая подробность. Разница в семантике.

ref и out в C#

ref и out в C#

Всем доброго времени суток, на связи Алексей Гулынин. В прошлой статье мы узнали про перегруженные методы в C#. В данной статье я бы хотел поговорить про ключевые слова ref и out в C#. Использование этих ключевых слов приводит к тому, что параметр начинает передаваться не по значению, а по ссылке. Ключевое слово out обозначает почти то же самое, что и ref . Параметр, перед которым указываются данные операторы, тоже передаётся по ссылке, т.е. в переменной хранится адрес этого параметра. В чем разница же между ref и out ? Разница в том, что out позволяет вам передать ссылку на переменную неинициализированную, а ref — только на инициализированную.

Давайте на примере разберем, что это значит. Создадим метод, который будет принимать числовой параметр. В данном методе этот параметр будем увеличивать и выводить значение. В методе Main() создадим параметр с этим же именем и будем также выводить его значение:

static void Main(string[] args) < int param = 10; testMethod(param); //Выйдет значение = 11 Console.WriteLine("Значение переменной param в методе Main = ", param); //Выйдет значение = 10 Console.ReadLine(); > static void testMethod(int param) < param++; Console.WriteLine("Значение переменной param в методе testMethod = ", param); >

Во втором случае вышло 10, потому что int — это ValueType (это когда параметр передаётся по значению), это значит, что при выполнении команды testMethod(param) метод testMethod() будет работать с копией переменной param (в данный момент происходит копирование переменной в стеке), что никак не повлияет на переменную param, которая была объявлена в методе Main(). Модифицируем наш код, добавив оператор ref перед переменной в методе testMethod(). Обратите внимание, что при вызове данного метода нужно также ставить оператор ref :

static void Main(string[] args) < int param = 10; testMethod(ref param); //Выйдет значение = 11 Console.WriteLine("Значение переменной param в методе Main = ", param); //Выйдет значение = 11 Console.ReadLine(); > static void testMethod(ref int param) < param++; Console.WriteLine("Значение переменной param в методе testMethod = ", param); >

Теперь программа в обоих случаях выводит 11. Тут дело в том, что мы в стек (это область памяти в пространстве адресов) загоняем ссылку на область памяти, где находится переменная param , и в методе testMethod() мы уже работаем не с копией, а с тем же самым параметром param , что и в методе Main() . Те изменения, которые мы провели с переменной param в методе testMethod() будут видны и снаружи (за областью видимости метода).

Внесем изменения в наш код. Просто объявим переменную param , но не будем её инициализировать, т.е. вместо строки int param = 10; запишем int param; . Компилятор старается контролировать использование неинициализированных переменных вы даст ошибку в этом случае. Мы пытаемся переменную, которая не содержит никаких значений, передать в метод и там с ней выполнять определенные действия, но так делать нельзя. А что делать если инициализация переменной выполняется как раз в этом методе?

В этом случае как раз и используют out :

static void Main(string[] args) < int param; testMethod(out param); //Выйдет значение = 11 Console.WriteLine("Значение переменной param в методе Main = ", param); //Выйдет значение = 11 Console.ReadLine(); > static void testMethod(out int param) < param = 10; param++; Console.WriteLine("Значение переменной param в методе testMethod = ", param); >

Теперь в методе testMethod() обязательно нужно проинициализировать переменную param.

В данной небольшой статье вы узнали про ключевые слова ref и out в C#.

На связи был Алексей Гулынин, оставляйте свои комментарии, увидимся в следующих статьях.

Что такое out в c

В прошлой теме был определен метод SayHello , который выводит на консоль некоторое сообщение:

void SayHello() < Console.WriteLine("Hello"); >SayHello(); // Hello

Но минусом подобного метода является то, что он выводит одно и то же сообщение. И было бы неплохо, если бы мы могли бы динамически определять, какое сообщение будет выводить метод на экран, то есть передать из вне в метод это сообщение. Для этого в языке C# мы можем использовать параметры.

Параметры позволяют передать в метод некоторые входные данные. Параметры определяются через заятую в скобках после названия метода в виде:

тип_метода имя_метода (тип_параметра1 параметр1, тип_параметра2 параметр2, . ) < // действия метода >

Определение параметра состоит из двух частей: сначала идет тип параметра и затем его имя.

Например, определим метод PrintMessage, который получает извне выводимое сообщение:

void PrintMessage(string message) < Console.WriteLine(message); >PrintMessage("Hello work"); // Hello work PrintMessage("Hello METANIT.COM"); // Hello METANIT.COM PrintMessage("Hello C#"); // Hello C#

Здесь метод PrintMessage() принимает один параметр, который называется message и имеет тип string .

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

PrintMessage("Hello work");

Здесь параметру message передается строка «Hello work». Значения, которые передаются параметрам, еще называются аргументами . То есть передаваемая строка «Hello work» в данном случае является аргументом.

Иногда можно встретить такие определения как формальные параметры и фактические параметры . Формальные параметры — это собственно параметры метода (в данном случае message), а фактические параметры — значения, которые передаются формальным параметрам. То есть фактические параметры — это и есть аргументы метода.

Определим еще один метод, который складывает два числа:

void Sum(int x, int y) < int result = x + y; Console.WriteLine($"+ = "); > Sum(10, 15); // 10 + 15 = 25

Метод Sum имеет два параметра: x и y. Оба параметра представляют тип int. Поэтому при вызове данного метода нам обязательно надо передать на место этих параметров два числа. Внутри метода вычисляется сумма переданных чисел и выводится на консоль.

При вызове метода Sum значения передаются параметрам по позиции. Например, в вызове Sum(10, 15) число 10 передается параметру x, а число 15 — параметру y.

Также параметры могут использоваться в сокращеной версии метода:

void Sum(int x, int y) => Console.WriteLine($" + = < x + y >"); Sum(10, 15); // 10 + 15 = 25

Передаваемые параметру значения могут представлять значения переменных или результат работы сложных выражений, которые возвращают некоторое значение:

void Sum(int x, int y) => Console.WriteLine($" + = < x + y >"); int a = 10, b = 15, c = 6; Sum(a, b); // 10 + 15 = 25 Sum(3, c); // 3 + 6 = 9 Sum(14, 4 + c); // 14 + 10 = 24

Если параметрами метода передаются значения переменных, то таким переменным должно быть присвоено значение. Например, следующая программа не скомпилируется:

void Sum(int x, int y) < Console.WriteLine($"+ = < x + y >"); > int a; int b = 15; Sum(a, b); // ! Ошибка

Соответствие параметов и аргументов по типу данных

При передаче значений параметрам важно учитывать тип параметров: между аргументами и параметрами должно быть соответствие по типу. Например:

void PrintPerson(string name, int age) < Console.WriteLine($"Name: Age: "); > PrintPerson("Tom", 24); // Name: Tom Age: 24

В данном случае первый параметр метода PrintPerson() представляет тип string , поэтому при вызове метода мы должны передать этому параметру значение типа string , то есть строку. Второй параметр представляет тип int , поэтому должны передать ему целое число, которое соответствует типу int .

PrintPerson("Tom", 24);

Также мы можем передать параметрам значения тех типов, которые автоматически могут быть преобразованы в тип параметров. Например:

void PrintPerson(string name, int age) < Console.WriteLine($"Name: Age: "); > byte b = 37; PrintPerson("Tom", b); // Name: Tom Age: 37

Здесь параметру типа int передается значение типа byte , но комилятор может автоматически преобразовать значение типа byte к тиу int. Поэтому здесь ошибки не возникнет. Какие преобразования типов могут быть выполнены автоматически, рассматривалось в одной из предыдущих тем: Преобразования базовых типов данных

Данные других типов мы передать параметров не можем. Например, следующий вызов метода PrintPerson будет ошибочным:

PrintPerson(45, "Bob"); // Ошибка! несоответствие значений типам параметров

Необязательные параметры

По умолчанию при вызове метода необходимо предоставить значения для всех его параметров. Но C# также позволяет использовать необязательные параметры. Для таких параметров нам необходимо объявить значение по умолчанию. Также следует учитывать, что после необязательных параметров все последующие параметры также должны быть необязательными:

void PrintPerson(string name, int age = 1, string company = "Undefined") < Console.WriteLine($"Name: Age: Company: "); >

Здесь параметры age и company являются необязательными, так как им присвоены значения. Поэтому при вызове метода мы можем не передавать для них данные:

void PrintPerson(string name, int age = 1, string company = "Undefined") < Console.WriteLine($"Name: Age: Company: "); > PrintPerson("Tom", 37, "Microsoft"); // Name: Tom Age: 37 Company: Microsoft PrintPerson("Tom", 37); // Name: Tom Age: 37 Company: Undefined PrintPerson("Tom"); // Name: Tom Age: 1 Company: Undefined

Консольный вывод программы:

Name: Tom Age: 37 Company: Microsoft Name: Tom Age: 37 Company: Undefined Name: Tom Age: 1 Company: Undefined

Именованные параметры

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

void PrintPerson(string name, int age = 1, string company = "Undefined") < Console.WriteLine($"Name: Age: Company: "); > PrintPerson("Tom", company:"Microsoft", age: 37); // Name: Tom Age: 37 Company: Microsoft PrintPerson(age:41, name: "Bob"); // Name: Bob Age: 41 Company: Undefined PrintPerson(company:"Google", name:"Sam"); // Name: Sam Age: 1 Company: Google

Для передачи значений параметрам о имени при вызове метода указывается имя параметра и через двоеточие его значение: name:»Tom»

Консольный вывод программы:

Name: Tom Age: 37 Company: Microsoft Name: Bob Age: 41 Company: Undefined Name: Sam Age: 1 Company: Google

Основы C# — ключевые слова Ref и Out

Когда мы передаем аргумент типа int, double, decimal и т.д. (базовые типы значений), мы передаем не фактическое значение, а его копию. Это означает, что наши исходные значения не меняются внутри методов, потому что мы передаем новую копию исходного значения. В результате все операции внутри метода выполняются с копируемым значением.

Мы можем показать это на примере:

class Program < public static void ChangeAndWrite(int number) < number = 10; Console.WriteLine($"Inside ChangeAndWrite method, number value is: "); > static void Main(string[] args) < int number = 5; Console.WriteLine($"Value of the number prior to ChangeAndWrite call is: "); ChangeAndWrite(number); Console.WriteLine($"Value of the number after the ChangeAndWrite call is: "); Console.ReadKey(); > > 

Как видите, значение числовой переменной изменяется только внутри метода ChangeAndWrite . Но исходное значение такое же, как и до вызова метода ChangeAndWrite . И снова это потому, что мы передаем точную копию исходного значения.

Использование ключевых слов Ref и Out

Мы можем изменить поведение по умолчанию. Если мы хотим изменить исходные значения внутри наших методов, мы можем сделать это с помощью ключевых слов ref и out внутри сигнатуры метода.

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

Итак, давайте упростим. Если мы хотим изменить существующее значение переменной внутри метода, мы будем использовать ключевое слово ref . Но если мы хотим присвоить совершенно новое значение переменной внутри метода, мы используем ключевое слово out .

Пример для типа значения

В предыдущем примере мы видели, как ведут себя переменные типа значения, если мы не используем ключевые слова ref или out. В этом примере мы увидим поведение переменных типа значения при использовании этих ключевых слов:

class Program < public static void ChangeRef(ref int numberRef) < numberRef = 25; Console.WriteLine($"Inside the ChangeRef method the numberRef is "); > public static void ChangeOut( out int numberOut) < numberOut = 60; Console.WriteLine($"Inside the ChangeOut method the numberOut is "); > static void Main(string[] args) < int numberRef = 15; Console.WriteLine($"Before calling the ChangeRef method the numberRef is "); ChangeRef(ref numberRef); Console.WriteLine($"After calling the ChangeRef method the numberRef is "); Console.WriteLine(); int numberOut; Console.WriteLine("Before calling the ChangeOut method the numberOut is unassigned"); ChangeOut(out numberOut); Console.WriteLine($"After calling the ChangeOut method the numberOut is "); Console.ReadKey(); > > 

Все это предельно ясно. Если мы используем ключевое слово ref или для out переменной типа значения, ее исходное значение изменится. Но разница в том, что с ключевым словом out мы можем использовать неинициализированные переменные.

Пример для ссылочного типа

Мы узнали, что ссылочный тип не хранит свое значение в собственной ячейке памяти. Он хранит адрес того места в памяти, где хранится значение. Поэтому, когда мы отправляем аргумент в качестве ссылочного типа методу и изменяем этот параметр, исходное значение изменяется. Это потому, что мы отправляем не копию значения, а копию адреса, который указывает на исходное значение. Это то же самое, что и при использовании ключевого слова ref с типами значений.

Тем не менее, мы можем использовать ключевое слово ref со ссылочными типами, если мы хотим создать новый объект с тем же адресом.

Давайте посмотрим это на примере:

class Program < public static void ChangeColor(Pen pen) < pen.Color = Color.Green; Console.WriteLine($"Inside the ChangeColor method the color is "); > public static void CreateNewObjectWithoutRef(Pen pen) < pen = new Pen(Color.Red); Console.WriteLine($"Inside the CreateNewObjectWithoutRef method the color of new pen object is "); > public static void CreateNewObjectWithRef(ref Pen pen) < pen = new Pen(Color.Yellow); Console.WriteLine($"Inside the CreateNewObjectWithRef method the color of new pen object is "); > static void Main(string[] args) < Pen pen = new Pen(Color.Blue); Console.WriteLine($"Before ChangeColor method: "); ChangeColor(pen); Console.WriteLine($"After the ChangeColor method: "); Console.WriteLine(); Console.WriteLine($"Before CreateNewObjectWithoutRef method: "); CreateNewObjectWithoutRef(pen); Console.WriteLine($"After CreateNewObjectWithoutRef method: "); Console.WriteLine(); Console.WriteLine($"Before CreateNewObjectWithRef method: "); CreateNewObjectWithRef(ref pen); Console.WriteLine($"After CreateNewObjectWithRef method: "); Console.ReadKey(); > > 

В первом методе мы не используем ключевое слово ref . Значение изменяется, потому что мы передаем копию адреса, в котором хранится исходное значение. Во втором методе исходное значение остается прежним. Это потому, что мы создаем новый объект внутри метода, таким образом выделяется новый адрес памяти. Но в третьем методе мы используем ключевое слово ref , и исходное значение изменяется. Зачем? Потому что с ref ключевым словом мы копируем тот же адрес в новый объект.

Резюме

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

В следующем посте мы поговорим о рекурсии и рекурсивных методах рекурсии и рекурсивных методах.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *