В C# любой тип является либо ссылочным, либо значимым типом. При работе со значением ссылочного типа, мы фактически всегда работаем с ссылкой на это значение. А в случае с типом значения мы работаем напрямую со значением. Отсюда следует что оператор присвоения копирует ссылку на значение для ссылочного типа и само значение для значимого.
Например int – это значимый тип. А любой тип массива – ссылочный.
Что выводится в консоль?
Фрагмент 1.25
int a = 0;
int b = a;
b = 10;
a = 15;
Console.WriteLine(a);
Console.WriteLine(b);
Правильный ответ: 10 и 15. И в этом нет ничего странного так, как в случае с типом значения (которым является тип int) при присвоение одной переменной другой, копируется значение переменной и в данном случае это само значения. Соответственно в один момент переменная b и a имеют одинаковое значение но это разные экземпляры одного значения так, что изменение одной переменной не приводит к изменению другой.
А что будет в этом случае?
Фрагмент 1.26
int[] array1 = new int[] { 0, 0 };
int[] array2 = array1;
array2[0] = 10;
array1[1] = 15;
Console.WriteLine(array1[0]);
Console.WriteLine(array1[1]);
Console.WriteLine(array2[0]);
Console.WriteLine(array2[1]);
Правильный ответ: 10 15 10 15
Переменные, с типом массива, содержат в себе ссылку на значение и при присваивании одной переменной к значению другой, мы копируем ссылку на один и тот же массив в памяти.
Всё это справедливо и для вызовов методов. Когда мы указываем переменную в качестве аргумента метода, мы просто берём значение переменной и передаем ее в метод.
Как думаете, что будет в консоли?
Фрагмент 1.28
static void Main(string[] args)
{
int a = 15;
int b = 10;
int c = 0;
Sum(a, b, c);
Console.WriteLine(c);
}
static void Sum(int a, int b, int c)
{
c= a + b;
}
Ответ ноль. Во-первых переменная “c” в методе Main и параметр “c” метода Sum никак не связаны. Во-вторых мы передаем в метод копию значения переменной c. Также хочу заметить что если вам нужно узнать в точке вызова результат работы метода, то для этого существует возвращаемое значение.
Фрагмент 1.29
static void Main(string[] args)
{
int a = 15;
int b = 10;
int c = 0;
c = Sum(a, b);
Console.WriteLine(c);
}
static int Sum(int a, int b)
{
return a + b;
}
Идём далее. А как вы думаете что будет если передать массив в метод? Так как он ссылочный, передаваться будет копия ссылки на массив в памяти. Ну и контрольный вопрос. Что мы увидим в консоли?
Фрагмент 1.30
static void Main(string[] args)
{
char[] map = new char[] { '_', '_', '!', '_', '_', '_'};
Generate(map);
for (int i = 0; i < map.Length; i++)
{
Console.Write(map[i]);
}
}
static void Generate(char[] map)
{
for(int i = 0; i < map.Length; i++)
{
map[i] = '#';
}
}
Как не странно линию из решёток.
Розы гибнут на газонах
А шпана на красных зонах
У вас должен возникнуть вполне резонный вопрос. Зачем я об этом рассказываю? В заголовке же написано “Модификаторы ref и out”. А ответ простой, весь этот текст рассказывает о том, что в метод нельзя передавать переменную. Что в него передается копия значения переменной.
И тут я такой хоба, достаю из широких штанин модификаторы которые… позволяют передавать ссылку на переменную и изменять её внутри метода. Давайте вспомним наш предыдущий пример с методом Sum и переменной “c” и попробуем выразить его таким образом, чтобы параметр “c” реально был привязан.
Фрагмент 1.31
static void Main(string[] args)
{
int a = 15;
int b = 10;
int c = 0;
Sum(a, b, ref c);
Console.WriteLine(c);
}
static void Sum(int a, int b, ref int c)
{
c = a + b;
}
При таком исполнение мы получаем действительно значение 25 в консоли потому, что указали что параметр “c” – это ссылка на какую-то переменную. И при вызове метода нам нужно указать какую-то переменную с тем же модификатором в качестве аргумента. После этого внутри метода изменяя параметр “c” мы изменяем внешнюю переменную.
Важно заметить что имя переменной и параметра может не совпадать.
Помимо модификатор ref у нас так же есть out. Технически они работают одинаково, но разность у них семантическая. Так ref говорит только о том, что будет ссылка на некую переменную которая должна иметь значение до вызова. А out говорит что переменная может быть пустая и метод обязан задать ей значение.
Например в методе Sum лучше было бы использовать out так, как метод Sum гарантирует, что в результате своего выполнения связанная переменная будет проинициализирована, а также ему не требуется какое-то изначальное значение переданной переменной.
Фрагмент 1.32
static void Main(string[] args)
{
int a = 15;
int b = 10;
int c;
Sum(a, b, out c);
Console.WriteLine(c);
}
static void Sum(int a, int b, out int c)
{
c = a + b;
}