Массив — это ссылка
В теме о переменных присваивание копировало значение: после b = a и a = 7 в b оставалось старое число. С массивами то же присваивание ведёт себя иначе:
int[] a = [1, 2, 3];
int[] b = a;
b[0] = 10;
Console.WriteLine(a[0]); // 10
Console.WriteLine(b[0]); // 10Давайте разберём по строкам:
int[] a = [1, 2, 3];создаёт массив и даёт ему имяa.int[] b = a;второго массива не создаёт. Теперь у того же массива два имени:aиb.b[0] = 10;меняет первый элемент этого единственного массива.- Поэтому
a[0]тоже равен10: черезaи черезbвидно одно и то же.
Вернёмся к шкафчикам. Ряд шкафчиков стоит в раздевалке, а у вас в руке ключ от этого ряда. Переменная массива — это ключ, а не сами шкафчики. Строка int[] b = a; делает дубликат ключа: ключей стало два, а ряд по-прежнему один. Откройте шкафчик первым ключом и положите туда вещь: вторым ключом вы найдёте её на том же месте.
Переменная хранит ссылку
Пока программа работает, все её значения, от чисел до массивов, хранятся в памяти компьютера: это и есть раздевалка, где стоят шкафчики. Сам массив лежит в памяти отдельно от переменной. В переменной хранится ссылка — значение, по которому программа находит массив в памяти. Это и есть ключ от ряда шкафчиков.
Типы, переменные которых хранят ссылку, называют ссылочными, и массивы — ссылочный тип. А int, double и bool — значимые типы: их переменная хранит само значение.
Присваивание копирует то, что лежит в переменной. Для int это само число, для массива — ссылка. Поэтому b = a копирует ключ, а не шкафчики.
В виджете у переменной массива вместо значения стоит стрелка: → [1, 2, 3] значит «ведёт к массиву с такими элементами». Нажимайте «Следующий шаг» и читайте подпись: она говорит, общий ли массив у имён.
1int[] a = [1, 2, 3];2int[] b = a;3b[0] = 10;4Console.WriteLine(a[0]);5ZeroLast(a);6Console.WriteLine(b[2]);7b = [7, 8, 9];8Console.WriteLine(a[0]);9Console.WriteLine(b[0]);10 11void ZeroLast(int[] nums)12{13 nums[nums.Length - 1] = 0;14}
Создаётся массив, и в a кладётся ссылка на него.
Пока две переменные хранят одну ссылку, запись через любое имя видна через другое. Метод, которому передали массив, получает ещё одно имя того же массива. Связь между именами пропадает, только когда одному из них присваивают другой массив.
Ключ можно поменять
Здесь картинка с ключами неточна. Настоящий ключ подходит к одному замку навсегда. А переменной-ссылке можно присвоить ссылку на другой массив, и дальше она ведёт уже к нему. Длина нового массива может быть любой: тип int[] длину не задаёт, она есть только у самого массива.
После b = [7, 8, 9] у b ключ от нового ряда, а ключ a по-прежнему открывает старый. Старый массив от этого не меняется: присваивание переменной не трогает элементы.
Массив в параметре метода
Параметр метода получает копию аргумента, поэтому метод не может изменить переданную переменную. Для массива правило то же, только копируется ссылка:
int[] prices = [100, 250, 80];
Discount(prices);
Console.WriteLine(prices[1]); // 225
Replace(prices);
Console.WriteLine(prices[1]); // 225
void Discount(int[] nums)
{
nums[1] = nums[1] - 25;
}
void Replace(int[] nums)
{
nums = [0, 0, 0];
}Разберём оба вызова:
Discount(prices)копирует в параметрnumsссылку изprices: у метода свой ключ от того же массива.nums[1] = nums[1] - 25меняет элемент общего массива, поэтому после вызоваprices[1]равен225.Replace(prices)тоже получает копию ссылки, но кладёт вnumsссылку на новый массив. Меняется только копия ключа.pricesпо-прежнему ведёт к старому массиву, и печатается снова225.
Этот вопрос часто задают на собеседовании: изменит ли метод массив, который ему передали? Ответ из двух частей:
- элементы — да: метод получает копию ссылки и меняет тот же массив;
- саму переменную вызывающего кода — нет: присваивание параметру меняет только копию ссылки.
С ref int[] nums метод получил бы саму переменную prices и смог бы положить в неё ссылку на новый массив. Без ref массив, как и int, передаётся по значению, а не по ссылке: копируется ссылка, которая лежит в переменной.
Настоящая копия
Иногда нужен второй ряд шкафчиков с теми же вещами, чтобы менять копию и не трогать оригинал. Для этого создают новый массив той же длины и копируют в него элементы методом Array.Copy:
int[] original = [1, 2, 3];
int[] copy = new int[original.Length];
Array.Copy(original, copy, original.Length);
copy[0] = 10;
Console.WriteLine(original[0]); // 1
Console.WriteLine(copy[0]); // 10Array.Copy(original, copy, original.Length) копирует из original в copy столько элементов, сколько указано третьим аргументом. После этого у массивов нет общих элементов и запись в copy не трогает original.
Частая ошибка
Два массива с одинаковыми элементами сравнили через ==:
int[] a = [1, 2, 3];
int[] b = [1, 2, 3];
bool same = a == b; // ✗
Console.WriteLine(same); // FalseПрограмма компилируется и печатает False, хотя элементы одинаковые. Для массивов == сравнивает ссылки, то есть проверяет, один ли это массив. Здесь массивов два, как два ряда шкафчиков с одинаковыми вещами. Чтобы сравнить содержимое, проверяют длины, а затем элементы по одному в цикле.
Главное
- Переменная массива хранит ссылку на массив, а не сами элементы: массивы — ссылочный тип.
int[] b = a;копирует ссылку: у массива становится два имени, и запись черезbвидна черезa.- Метод, получивший массив, может изменить его элементы, но присваивание параметру не меняет переменную вызывающего кода.
- Настоящую копию делают через новый массив и
Array.Copy;==для массивов проверяет, один ли это массив.