Равенство: == и Equals
Возьмите две ксерокопии одного отчёта. Это два разных листа, но текст на них одинаковый. А две ссылки на облачный документ ведут к одному и тому же документу. Равны ли две вещи, зависит от вопроса: «это одно и то же?» или «в них одно и то же?».
У строк == отвечает на второй вопрос: он сравнивает текст. Для объектов класса, как вы видели в прошлой теме, == отвечает на первый. А ещё у каждого объекта есть метод Equals. Что отвечает он и как ведут себя структуры? Посмотрим сначала на класс:
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; }Давайте разберём пример по строкам:
aиb— два разных объекта с одинаковым полемX = 1.a == bпечатаетFalse: для класса==проверяет, один ли это объект, а поля не сравнивает.Spot c = a;копирует ссылку, поэтомуa == cпечатаетTrue: обе ссылки ведут к одному объекту.Equalsу класса по умолчанию тоже сравнивает ссылки, поэтому строка печатаетFalse.
Два вида равенства
Вопрос «это одно и то же?» и вопрос «в них одно и то же?» — это два разных вида равенства:
- ссылочное равенство — две ссылки ведут к одному объекту, как две ссылки на один облачный документ;
- равенство по значению — совпадают данные, как текст на двух ксерокопиях.
Для классов и ==, и Equals по умолчанию проверяют ссылочное равенство. Два объекта с одинаковыми полями — всё равно два объекта.
Здесь картинка с листами неточна. Человек, глядя на две ксерокопии, сам решает, какой вопрос задать. C# выбирает вопрос за вас, по виду типа: для класса — «это один объект?», для Equals у структуры — «совпадают ли поля?».
Equals у структур
У структуры нет ссылки, которую можно сравнить: значение хранится прямо в переменной. Поэтому Equals у структуры по умолчанию сравнивает все поля:
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, и точки стали разными.
Две упаковки одного числа
Упаковка создаёт объект, поэтому два упакованных числа — две разные коробки:
object first = 5;
object second = 5;
Console.WriteLine(first == second); // False
Console.WriteLine(first.Equals(second)); // TrueРазберём две последние строки:
first == secondпечатаетFalse: у переменных типаobjectоператор==сравнивает ссылки, а коробок две.first.Equals(second)печатаетTrue:Equalsу упакованногоintсравнивает сами числа.
Строки ведут себя иначе. string — ссылочный тип, но и ==, и Equals у него сравнивают текст: так их задали авторы типа string. Класс может сам решить, что для него значат == и Equals.
== или Equals
Этот вопрос часто задают на собеседовании: чем == отличается от Equals? Ответ зависит от вида типа:
- у класса по умолчанию оба сравнивают ссылки;
- у вашей структуры
Equalsсравнивает поля, а==без собственного объявления не компилируется (у встроенныхint,doubleиboolон уже есть); - у
stringоба сравнивают текст.
Частая ошибка
Две структуры попробовали сравнить через ==:
Point m = new Point();
Point n = new Point();
Console.WriteLine(m == n); // ✗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.