Как работать с объектами: 2‑я часть гайда по ООП
Только работа с объектами начинает казаться простой, как тут же появляются новые сюрпризы. Рассказываем, как их избежать.


vlada_maestro / shutterstock
Это вторая статья из цикла про ООП. Поэтому советуем сначала прочесть предыдущую часть.
Многие новички сталкиваются с тем, что объекты ведут себя не так, как планировалось, хотя всё вроде бы написано правильно. Кроме того, такой код только что корректно работал с обычными переменными.
На самом деле никакой ошибки, скорее всего, нет. А все дело в том, что работа с объектами отличается от работы с обычными переменными. Разбираемся, почему так происходит.
Все статьи про ООП
Работа со ссылочным типом данных
Данные, с которыми работает программа, хранятся в оперативной памяти. Для этого используются ячейки, у которых есть адрес (ключ) и значение.
Раньше программистам приходилось запоминать эти ключи или записывать их в отдельный блокнот. Но современные языки программирования позволяют создавать переменные (их называют примитивными или значимыми типами данных), у которых вместо адреса идентификатор:
int sum = 50; //sum — ключ, 50 — значение
string name = "John";
Для каждой переменной выделяется одна ячейка памяти. Однако объекты гораздо больше обычной переменной, потому что в них может быть сразу несколько полей. Можно создать переменную hero, в которой будет объект класса Character:
Character hero = new Character();
Но в ней будет храниться не сам объект, а ссылка на него. Поэтому объекты относятся к ссылочным типам данных, и из-за этого работа с ними отличается от работы с примитивными типами.
Рассмотрим такой код:
Character hero = new Character("Player", 50, 20);
Character hero1 = new Character("Player", 50, 20);
if (hero == hero1)
{
Console.WriteLine("true");
}
else
{
Console.WriteLine("false");
}
Console.ReadKey();
Здесь сначала создаются, а потом сравниваются два идентичных объекта. Если они равны, то выводится true, а иначе — false. Вот что выведет программа:

Несмотря на то что все поля совпадают, программа выводит false. И это не баг. Дело в том, что сравниваются не значения полей объектов, а ссылки на них. А поскольку это два различных объекта, то и адреса у них разные.
Строка true будет выведена только в том случае, если в обеих переменных находится ссылка на один и тот же объект. Чтобы это проверить, в коде из примера выше нужно изменить значение переменной hero1:
Character hero1 = hero;
Теперь программа выведет true, потому что это один и тот же объект:

И теперь, если изменить hero1, hero тоже изменится:
Character hero = new Character("Player", 50, 20);
Character hero1 = hero;
hero1.Move("up");
Console.WriteLine(hero.Coordinates);
Console.ReadKey();
Тут меняются координаты объекта hero1 с помощью метода Move (), а затем выводятся координаты объекта hero. Они были [50, 20], но теперь по оси Y значение будет 21:

Если нужно проверить, совпадают ли значения полей у объектов, то надо вручную проверять каждое поле:
if (hero.X == hero1.X && hero.Y == hero1.Y)
{
Console.WriteLine("true");
}
else
{
Console.WriteLine("false");
}
Однако это неудобно, если полей очень много. Чтобы упростить эту задачу, можно использовать метод Equals () — его мы рассмотрим в статье про полиморфизм. Если же надо сделать копию, то можно написать метод Clone () или Copy (). Он будет принимать объект, чтобы скопировать его данные:
public void Clone(Character obj)
{
this.name = obj.name;
this.x = obj.x;
this.y = obj.y;
this.health = obj.health;
}
Или же можно написать конструктор, который будет принимать в качестве аргумента другой объект.
Использование объекта в качестве аргумента
Сразу же стоит отметить, что у передачи объекта в качестве аргумента есть побочные эффекты. Рассмотрим такой код:
static void Main(string[] args)
{
int num = 2;
Multiply(num);
Console.WriteLine(num);
Console.ReadKey();
}
private static void Multiply(int num)
{
num *= 2;
}
В методе Multiply () полученное число умножится на два. Но так как int — это значимый тип, то в метод будет передана не ссылка на это значение в памяти, а само число. То есть будет создана новая переменная (их называют локальными) с таким же значением, и уже она будет умножена на два. На переданное значение это никак не повлияет.
Вот что выведет программа:

Однако если использовать объект, то будет передана ссылка на него, а все операции будут проводиться с ним, а не с его значением:
static void Main(string[] args)
{
Character hero = new Character(50, 20);
Change(hero);
Console.WriteLine(hero.Health);
Console.ReadKey();
}
private static void Change(Character hero)
{
hero.Health = 4;
}
Вот что выведет программа:

Такие особенности ссылочных типов нужно учитывать при разработке программ, чтобы не происходило ничего неожиданного. Например, когда значение меняется, хотя и не должно.
Домашнее задание
Создайте класс Game, в котором будут храниться все игровые объекты. Дайте персонажу возможность перемещаться по карте и взаимодействовать с объектами, координаты которых совпадают с его позицией.
Подсказка
Используйте коллекции и циклы.
Заключение
Практикуйтесь и работайте с объектами как можно чаще, и тогда вы сможете овладеть ими. Однако, чтобы писать код без костылей, нужно понимать, как он работает, — в том числе это касается и ссылочных, и значимых типов данных. Кроме того, понимание различий между этими типами избавит вас от неприятных сюрпризов.