Перейти к содержимому
Теория · Значимые и ссылочные типы · урок 10 из 11

Равенство: == и Equals

Возьмите две ксерокопии одного отчёта. Это два разных листа, но текст на них одинаковый. А две ссылки на облачный документ ведут к одному и тому же документу. Равны ли две вещи, зависит от вопроса: «это одно и то же?» или «в них одно и то же?».

У строк == отвечает на второй вопрос: он сравнивает текст. Для объектов класса, как вы видели в прошлой теме, == отвечает на первый. А ещё у каждого объекта есть метод Equals. Что отвечает он и как ведут себя структуры? Посмотрим сначала на класс:

C#
Spot a = new Spot();
a.X = 1;
Spot b = new Spot();
b.X = 1;
Spot c = a;
Console.WriteLine(a == b);      // False
Console.WriteLine(a == c);      // True
Console.WriteLine(a.Equals(b)); // False

class Spot { public int X; }

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

  1. a и b — два разных объекта с одинаковым полем X = 1.
  2. a == b печатает False: для класса == проверяет, один ли это объект, а поля не сравнивает.
  3. Spot c = a; копирует ссылку, поэтому a == c печатает True: обе ссылки ведут к одному объекту.
  4. Equals у класса по умолчанию тоже сравнивает ссылки, поэтому строка печатает False.

Два вида равенства

Вопрос «это одно и то же?» и вопрос «в них одно и то же?» — это два разных вида равенства:

  • ссылочное равенство — две ссылки ведут к одному объекту, как две ссылки на один облачный документ;
  • равенство по значению — совпадают данные, как текст на двух ксерокопиях.

Для классов и ==, и Equals по умолчанию проверяют ссылочное равенство. Два объекта с одинаковыми полями — всё равно два объекта.

Здесь картинка с листами неточна. Человек, глядя на две ксерокопии, сам решает, какой вопрос задать. C# выбирает вопрос за вас, по виду типа: для класса — «это один объект?», для Equals у структуры — «совпадают ли поля?».

Equals у структур

У структуры нет ссылки, которую можно сравнить: значение хранится прямо в переменной. Поэтому Equals у структуры по умолчанию сравнивает все поля:

C#
Point p = new Point();
p.X = 1;
Point q = new Point();
q.X = 1;
Console.WriteLine(p.Equals(q)); // True
q.X = 2;
Console.WriteLine(p.Equals(q)); // False

struct Point { public int X; }

Пока поля p и q совпадают, Equals печатает True. Стоило изменить q.X, и точки стали разными.

Две упаковки одного числа

Упаковка создаёт объект, поэтому два упакованных числа — две разные коробки:

C#
object first = 5;
object second = 5;
Console.WriteLine(first == second);      // False
Console.WriteLine(first.Equals(second)); // True

Разберём две последние строки:

  1. first == second печатает False: у переменных типа object оператор == сравнивает ссылки, а коробок две.
  2. first.Equals(second) печатает True: Equals у упакованного int сравнивает сами числа.

Строки ведут себя иначе. string — ссылочный тип, но и ==, и Equals у него сравнивают текст: так их задали авторы типа string. Класс может сам решить, что для него значат == и Equals.

== или Equals

Этот вопрос часто задают на собеседовании: чем == отличается от Equals? Ответ зависит от вида типа:

  • у класса по умолчанию оба сравнивают ссылки;
  • у вашей структуры Equals сравнивает поля, а == без собственного объявления не компилируется (у встроенных int, double и bool он уже есть);
  • у string оба сравнивают текст.

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

Две структуры попробовали сравнить через ==:

C#
Point m = new Point();
Point n = new Point();
Console.WriteLine(m == n); // ✗
TEXT
error CS0019: Operator '==' cannot be applied to operands of type 'Point' and 'Point'

Программа не компилируется. Для структуры C# не знает, что значит ==, пока структура сама не объявит этот оператор. Сравнивайте структуры через Equals: m.Equals(n) даёт true, потому что у обеих точек все поля равны нулю.

Главное

  • Для классов == и Equals по умолчанию проверяют ссылочное равенство: один ли это объект.
  • Equals у структуры сравнивает все поля, а == для структуры без своего объявления не компилируется.
  • Два упакованных числа — два объекта: == даёт false, а Equals — true.
  • string сравнивает текст и через ==, и через Equals.