Объект и ссылка на него
В теме о массивах присваивание int[] b = a; не создавало второй массив, а давало первому ещё одно имя. С объектами классов происходит то же самое:
var a = new House("red");
House b = a;
b.Color = "blue";
Console.WriteLine(a.Color); // blue
class House
{
public string Color { get; set; }
public House(string color) => Color = color;
}Давайте разберём по строкам:
var a = new House("red");строит красный дом и даёт ему имяa.House b = a;второго дома не строит. Теперь у того же дома два имени:aиb.b.Color = "blue";перекрашивает этот единственный дом.- Поэтому
Console.WriteLine(a.Color)печатаетblue: черезaи черезbвиден один и тот же дом.
Представьте, что дом стоит на улице, а у вас в кармане бумажка с его адресом. Переменная House a — это бумажка, а не дом. Строка House b = a; переписывает адрес на вторую бумажку: бумажек стало две, а дом по-прежнему один. Кто бы ни пришёл по этому адресу с кистью, перекрасит он тот самый дом.
Переменная хранит ссылку
Класс — ссылочный тип, как и массив. Объект лежит в памяти отдельно, а в переменной хранится ссылка на него — тот самый адрес на бумажке. Присваивание копирует то, что лежит в переменной, поэтому b = a копирует адрес, а не дом.
В виджете у переменной объекта вместо значения стоит стрелка и краткая форма объекта. → House { Color = "red" } читается так: переменная ведёт к дому, у которого Color равен "red". Нажимайте «Следующий шаг» и читайте подпись: она говорит, общий ли дом у имён.
1var a = new House("red");2House b = a;3b.Color = "blue";4Console.WriteLine(a.Color);5Repaint(a);6Console.WriteLine(b.Color);7b = new House("red");8Console.WriteLine(a.Color);9Console.WriteLine(b.Color);10 11void Repaint(House h)12{13 h.Color = "green";14}15 16class House17{18 public string Color { get; set; }19 20 public House(string color)21 {22 Color = color;23 }24}
Строится дом, и в переменную a кладётся ссылка на него.
Пока две переменные хранят одну ссылку, изменение через любое имя видно через другое. Метод, которому передали объект, получает ещё одну копию ссылки и меняет тот же дом. На собеседовании часто спрашивают, что произойдёт после b = a, если тип a — класс. Ответ: обе переменные ведут к одному объекту, и изменения через одну видны через другую.
Бумажку можно переписать
Адрес на бумажке можно стереть и записать новый. После b = new House("red") переменная b ведёт к новому дому, а a — к старому. Старый дом от этого не меняется: присваивание переменной не трогает объект.
То же с параметром метода. Если внутри Repaint написать h = new House("pink");, изменится только копия ссылки в h. Переменная a в вызывающем коде по-прежнему будет вести к старому дому.
Ссылка в никуда: null
Здесь бумажка и ссылка расходятся. На бумажке можно ошибиться в адресе и прийти не туда, а ссылку в C# так не испортить: она либо ведёт к настоящему объекту, либо пустая.
Пустая ссылка не ведёт ни к какому объекту и записывается словом null. Это бумажка, на которой адрес не записан. Такая ссылка бывает в переменной или в элементе массива, у которого элементы ссылочного типа, например House:
House[] street = new House[3];
Console.WriteLine(street[0] == null); // True
street[0] = new House("red");
Console.WriteLine(street[0].Color); // red
Console.WriteLine(street[1] == null); // True
class House
{
public string Color { get; set; }
public House(string color) => Color = color;
}Массив чисел new int[3] заполнен нулями, а массив домов new House[3] — пустыми ссылками. Это три бумажки без адресов: ни одного дома ещё не построено. Дом появляется, только когда в элемент кладут результат new.
Сравнение через ==
Для объектов классов, которые вы пишете сами, == сравнивает ссылки, как и для массивов: проверяет, один ли это дом, а не одинаковы ли дома.
var a = new House("red");
var b = new House("red");
House c = a;
Console.WriteLine(a == b); // False
Console.WriteLine(a == c); // True
class House
{
public string Color { get; set; }
public House(string color) => Color = color;
}У a и b один цвет, но это два разных дома по двум адресам, поэтому a == b даёт false. А c — вторая бумажка с адресом дома a, и a == c даёт true.
Частая ошибка
Массив объектов создали, а сами объекты — нет:
House[] street = new House[3];
street[0].Color = "red"; // ✗
Console.WriteLine(street[0].Color);
class House
{
public string Color { get; set; }
public House(string color) => Color = color;
}Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.Программа компилируется, но при запуске останавливается на второй строке с исключением NullReferenceException. В street[0] лежит null: адреса нет, и идти с кистью некуда. Компилятор здесь не предупреждает: он не следит, какие элементы массива уже получили объекты. Сначала постройте дом: street[0] = new House("red");.
Главное
- Переменная, тип которой — класс, хранит ссылку на объект:
House b = a;копирует ссылку и даёт тому же объекту второе имя. - Изменение объекта через одно имя видно через все остальные; метод, получивший объект, меняет тот же объект.
- Присваивание переменной новой ссылки,
b = new House("red"), меняет только эту переменную, а не объект. null— ссылка, которая никуда не ведёт: обращение через неё к полю или свойству останавливает программу сNullReferenceException.