Когда мы передаем аргумент типа int, double, decimal и т.д. (базовые типы значений), мы передаем не фактическое значение, а его копию. Это означает, что наши исходные значения не меняются внутри методов, потому что мы передаем новую копию исходного значения. В результате все операции внутри метода выполняются с копируемым значением.
Мы можем показать это на примере:
class Program
{
public static void ChangeAndWrite(int number)
{
number = 10;
Console.WriteLine($"Inside ChangeAndWrite method, number value is: {number}");
}
static void Main(string[] args)
{
int number = 5;
Console.WriteLine($"Value of the number prior to ChangeAndWrite call is: {number}");
ChangeAndWrite(number);
Console.WriteLine($"Value of the number after the ChangeAndWrite call is: {number}");
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 {numberRef}");
}
public static void ChangeOut( out int numberOut)
{
numberOut = 60;
Console.WriteLine($"Inside the ChangeOut method the numberOut is {numberOut}");
}
static void Main(string[] args)
{
int numberRef = 15;
Console.WriteLine($"Before calling the ChangeRef method the numberRef is {numberRef}");
ChangeRef(ref numberRef);
Console.WriteLine($"After calling the ChangeRef method the numberRef is {numberRef}");
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 {numberOut}");
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 {pen.Color}");
}
public static void CreateNewObjectWithoutRef(Pen pen)
{
pen = new Pen(Color.Red);
Console.WriteLine($"Inside the CreateNewObjectWithoutRef method the color of new pen object is {pen.Color}");
}
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 {pen.Color}");
}
static void Main(string[] args)
{
Pen pen = new Pen(Color.Blue);
Console.WriteLine($"Before ChangeColor method: {pen.Color}");
ChangeColor(pen);
Console.WriteLine($"After the ChangeColor method: {pen.Color}");
Console.WriteLine();
Console.WriteLine($"Before CreateNewObjectWithoutRef method: {pen.Color}");
CreateNewObjectWithoutRef(pen);
Console.WriteLine($"After CreateNewObjectWithoutRef method: {pen.Color}");
Console.WriteLine();
Console.WriteLine($"Before CreateNewObjectWithRef method: {pen.Color}");
CreateNewObjectWithRef(ref pen);
Console.WriteLine($"After CreateNewObjectWithRef method: {pen.Color}");
Console.ReadKey();
}
}
В первом методе мы не используем ключевое слово ref
. Значение изменяется, потому что мы передаем копию адреса, в котором хранится исходное значение. Во втором методе исходное значение остается прежним. Это потому, что мы создаем новый объект внутри метода, таким образом выделяется новый адрес памяти. Но в третьем методе мы используем ключевое слово ref
, и исходное значение изменяется. Зачем? Потому что с ref
ключевым словом мы копируем тот же адрес в новый объект.
Резюме
Теперь мы знаем, как использовать ключевые слова ref и out с типами значений и ссылок. Это довольно полезная функция в C#, поэтому знание того, как работать с этими ключевыми словами, является преимуществом для разработчиков.
В следующем посте мы поговорим о рекурсии и рекурсивных методах рекурсии и рекурсивных методах.