Перегрузка операторов сравнения
Реализуйте операторы ==, != и операторы упорядочивания.
«Перегрузка операторов сравнения» — бесплатный урок C# Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Операторы сравнения и равенство
Если Вы перегружаете ==, необходимо также перегрузить !=, а также переопределить Equals и GetHashCode, чтобы все формы проверки равенства оставались согласованными. C# проверяет наличие парных операторов во время компиляции.
Перегрузка == и !=
Компилятор требует, чтобы == и != определялись вместе. Каждый из них возвращает значение bool, показывающее, считаются ли операнды равными.
using System;
public struct Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
public static bool operator !=(Point a, Point b) => !(a == b);
public override bool Equals(object obj) => obj is Point p && this == p;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Point(1, 2) == new Point(1, 2));
Console.WriteLine(new Point(1, 2) != new Point(3, 4));
}
}Всегда переопределяйте Equals
Операторы разрешаются во время компиляции на основе статического типа, а Equals является виртуальным и используется коллекциями. Переопределяйте Equals, чтобы словари и List.Contains согласовывались с Вашим ==.
using System;
public struct Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
public static bool operator !=(Point a, Point b) => !(a == b);
public override bool Equals(object obj) => obj is Point p && this == p;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
object a = new Point(1, 2);
object b = new Point(1, 2);
Console.WriteLine(a.Equals(b));
}
}GetHashCode должен соответствовать равенству
Если два значения равны, они должны возвращать один и тот же хеш-код. В противном случае коллекции на основе хеширования, такие как Dictionary и HashSet, будут работать неправильно.
using System;
using System.Collections.Generic;
public struct Point
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public static bool operator ==(Point a, Point b) => a.X == b.X && a.Y == b.Y;
public static bool operator !=(Point a, Point b) => !(a == b);
public override bool Equals(object obj) => obj is Point p && this == p;
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var set = new HashSet<Point> { new Point(1, 2) };
Console.WriteLine(set.Contains(new Point(1, 2)));
}
}Операторы отношения < и >
Вы также можете перегрузить операторы отношения. Как и для равенства, < и > должны определяться вместе; то же самое относится к <= и >=.
using System;
public struct Weight
{
public int Grams;
public Weight(int g) { Grams = g; }
public static bool operator <(Weight a, Weight b) => a.Grams < b.Grams;
public static bool operator >(Weight a, Weight b) => a.Grams > b.Grams;
public override string ToString() => Grams + "g";
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Weight(100) < new Weight(200));
Console.WriteLine(new Weight(300) > new Weight(200));
}
}Равенство ссылочных типов
Для классов равенство значений означает сравнение полей, а не ссылок. Внимательно обрабатывайте значение null, чтобы оператор не выдавал исключение.
using System;
public class Person
{
public string Name;
public Person(string name) { Name = name; }
public static bool operator ==(Person a, Person b)
{
if (ReferenceEquals(a, b)) return true;
if (a is null || b is null) return false;
return a.Name == b.Name;
}
public static bool operator !=(Person a, Person b) => !(a == b);
public override bool Equals(object obj) => this == (obj as Person);
public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Person("Ann") == new Person("Ann"));
}
}Согласованность всех форм
Все способы проверки равенства должны давать один результат: ==, Equals и хеш-код. Реализуйте == через Equals (или наоборот), чтобы существовал единый источник истины.
using System;
public struct Id
{
public int Value;
public Id(int value) { Value = value; }
public bool Equals(Id other) => Value == other.Value;
public override bool Equals(object obj) => obj is Id other && Equals(other);
public override int GetHashCode() => Value;
public static bool operator ==(Id a, Id b) => a.Equals(b);
public static bool operator !=(Id a, Id b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Id(7) == new Id(7));
Console.WriteLine(new Id(7).Equals(new Id(8)));
}
}Операторы и Equals в коллекциях
Поиск в списке использует Equals, а не ==, поскольку работает со ссылками типа object. Если Вы перегрузите только == и забудете про Equals, поиск будет использовать идентичность ссылок и может завершиться неудачей.
using System;
using System.Collections.Generic;
public struct Tag
{
public string Name;
public Tag(string name) { Name = name; }
public override bool Equals(object obj) => obj is Tag t && t.Name == Name;
public override int GetHashCode() => Name?.GetHashCode() ?? 0;
public static bool operator ==(Tag a, Tag b) => a.Equals(b);
public static bool operator !=(Tag a, Tag b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
var list = new List<Tag> { new Tag("red"), new Tag("blue") };
Console.WriteLine(list.Contains(new Tag("blue")));
}
}Корректное подавление предупреждений компилятора
Переопределение Equals и GetHashCode вместе с операторами — это не просто вопрос хорошего тона: компилятор выдаёт предупреждение, если Вы перегружаете ==, но не переопределяете Equals. Выполнение обоих действий устраняет предупреждение и предотвращает ошибки.
using System;
public struct Cell
{
public int Row, Col;
public Cell(int r, int c) { Row = r; Col = c; }
public override bool Equals(object obj) => obj is Cell c && c.Row == Row && c.Col == Col;
public override int GetHashCode() => HashCode.Combine(Row, Col);
public static bool operator ==(Cell a, Cell b) => a.Equals(b);
public static bool operator !=(Cell a, Cell b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Cell(0, 0) == new Cell(0, 0));
}
}Объединение всех элементов
Корректно реализующий равенство тип определяет == и !=, переопределяет Equals и GetHashCode, а также направляет их все через одно сравнение. Это эталонный подход для типов, подобных значениям.
using System;
using System.Collections.Generic;
public struct Color
{
public int R, G, B;
public Color(int r, int g, int b) { R = r; G = g; B = b; }
public override bool Equals(object obj) => obj is Color c && c.R == R && c.G == G && c.B == B;
public override int GetHashCode() => HashCode.Combine(R, G, B);
public static bool operator ==(Color a, Color b) => a.Equals(b);
public static bool operator !=(Color a, Color b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
var dict = new Dictionary<Color, string> { [new Color(255, 0, 0)] = "red" };
Console.WriteLine(dict[new Color(255, 0, 0)]);
}
}Попробуйте сами
Реализуйте структуру с полностью согласованным сравнением на равенство и убедитесь, что она одновременно работает как ключ словаря и с оператором ==.
using System;
using System.Collections.Generic;
public struct GridPos
{
public int Row, Col;
public GridPos(int r, int c) { Row = r; Col = c; }
public override bool Equals(object obj) => obj is GridPos p && p.Row == Row && p.Col == Col;
public override int GetHashCode() => HashCode.Combine(Row, Col);
public static bool operator ==(GridPos a, GridPos b) => a.Equals(b);
public static bool operator !=(GridPos a, GridPos b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
var grid = new Dictionary<GridPos, string> { [new GridPos(1, 1)] = "player" };
Console.WriteLine(new GridPos(1, 1) == new GridPos(1, 1));
Console.WriteLine(grid[new GridPos(1, 1)]);
}
}Быстрая проверка
Вспомните правила перегрузки операторов равенства.
Итоги
Перегрузка операторов сравнения требует согласованности.
==и!=должны определяться вместе; то же самое относится к</>.- Переопределяйте
EqualsиGetHashCodeв соответствии с==. - Равные значения должны иметь один и тот же хеш-код.
- Направляйте все проверки равенства через один метод сравнения, чтобы избежать расхождений.
Часто задаваемые вопросы
Урок «Перегрузка операторов сравнения» бесплатный?
Да — полный текст урока «Перегрузка операторов сравнения» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Перегрузка операторов сравнения»?
Реализуйте операторы ==, != и операторы упорядочивания. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Перегрузка операторов сравнения»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Перегрузка арифметических операторов
- Перегрузка операторов сравнения
- Пользовательские преобразования
- Рекомендации по перегрузке операторов