Перейти к содержимому
Теория · Классы и объекты · урок 10 из 11

Объект и ссылка на него

В теме о массивах присваивание int[] b = a; не создавало второй массив, а давало первому ещё одно имя. С объектами классов происходит то же самое:

C#
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;
}

Давайте разберём по строкам:

  1. var a = new House("red"); строит красный дом и даёт ему имя a.
  2. House b = a; второго дома не строит. Теперь у того же дома два имени: a и b.
  3. b.Color = "blue"; перекрашивает этот единственный дом.
  4. Поэтому Console.WriteLine(a.Color) печатает blue: через a и через b виден один и тот же дом.

Представьте, что дом стоит на улице, а у вас в кармане бумажка с его адресом. Переменная House a — это бумажка, а не дом. Строка House b = a; переписывает адрес на вторую бумажку: бумажек стало две, а дом по-прежнему один. Кто бы ни пришёл по этому адресу с кистью, перекрасит он тот самый дом.

Переменная хранит ссылку

Класс — ссылочный тип, как и массив. Объект лежит в памяти отдельно, а в переменной хранится ссылка на него — тот самый адрес на бумажке. Присваивание копирует то, что лежит в переменной, поэтому b = a копирует адрес, а не дом.

В виджете у переменной объекта вместо значения стоит стрелка и краткая форма объекта. → House { Color = "red" } читается так: переменная ведёт к дому, у которого Color равен "red". Нажимайте «Следующий шаг» и читайте подпись: она говорит, общий ли дом у имён.

ВживуюДва имени — один домшаг 1 из 10 · рамок: 1

Строится дом, и в переменную a кладётся ссылка на него.

Подпись к шагу говорит, общий ли дом у имён

Пока две переменные хранят одну ссылку, изменение через любое имя видно через другое. Метод, которому передали объект, получает ещё одну копию ссылки и меняет тот же дом. На собеседовании часто спрашивают, что произойдёт после b = a, если тип a — класс. Ответ: обе переменные ведут к одному объекту, и изменения через одну видны через другую.

Бумажку можно переписать

Адрес на бумажке можно стереть и записать новый. После b = new House("red") переменная b ведёт к новому дому, а a — к старому. Старый дом от этого не меняется: присваивание переменной не трогает объект.

То же с параметром метода. Если внутри Repaint написать h = new House("pink");, изменится только копия ссылки в h. Переменная a в вызывающем коде по-прежнему будет вести к старому дому.

Ссылка в никуда: null

Здесь бумажка и ссылка расходятся. На бумажке можно ошибиться в адресе и прийти не туда, а ссылку в C# так не испортить: она либо ведёт к настоящему объекту, либо пустая.

Пустая ссылка не ведёт ни к какому объекту и записывается словом null. Это бумажка, на которой адрес не записан. Такая ссылка бывает в переменной или в элементе массива, у которого элементы ссылочного типа, например House:

C#
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.

Сравнение через ==

Для объектов классов, которые вы пишете сами, == сравнивает ссылки, как и для массивов: проверяет, один ли это дом, а не одинаковы ли дома.

C#
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.

Частая ошибка

Массив объектов создали, а сами объекты — нет:

C#
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;
}
TEXT
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.