Реализация IEquatable
Обеспечьте типобезопасное сравнение на равенство с помощью Equals.
«Реализация IEquatable» — бесплатный урок C# Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.
Зачем нужен IEquatable<T>
IEquatable<T> предоставляет строго типизированный метод Equals(T other). Он позволяет избежать упаковки для типов-значений и приведения типов для ссылочных типов, делая проверку равенства более быстрой и понятной.
Реализация интерфейса
Реализуйте IEquatable<T> и предоставьте Equals(T other). Также следует переопределить унаследованные object.Equals и GetHashCode, чтобы все способы сравнения оставались согласованными.
using System;
public struct Point : IEquatable<Point>
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public bool Equals(Point other) => X == other.X && Y == other.Y;
public override bool Equals(object obj) => obj is Point p && Equals(p);
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Point(1, 2).Equals(new Point(1, 2)));
}
}Как избежать упаковки структур
Негенерический метод object.Equals(object) упаковывает аргумент-структуру. Типизированный Equals(Point other) из IEquatable<T> принимает структуру напрямую, поэтому выделения памяти не происходит.
using System;
public struct Point : IEquatable<Point>
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public bool Equals(Point other) => X == other.X && Y == other.Y; // no boxing
public override bool Equals(object obj) => obj is Point p && Equals(p);
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
Point a = new Point(3, 4);
Point b = new Point(3, 4);
Console.WriteLine(a.Equals(b)); // calls the typed overload
}
}Коллекции используют IEquatable<T>
List<T>.Contains, Dictionary и HashSet при наличии предпочитают IEquatable<T>.Equals. Его реализация делает эти операции эффективными и корректными для вашего типа.
using System;
using System.Collections.Generic;
public struct Point : IEquatable<Point>
{
public int X, Y;
public Point(int x, int y) { X = x; Y = y; }
public bool Equals(Point other) => X == other.X && Y == other.Y;
public override bool Equals(object obj) => obj is Point p && Equals(p);
public override int GetHashCode() => HashCode.Combine(X, Y);
}
public class Program
{
public static void Main()
{
var list = new List<Point> { new Point(0, 0), new Point(1, 1) };
Console.WriteLine(list.Contains(new Point(1, 1)));
}
}Ссылочные типы и IEquatable<T>
Классы также могут реализовывать IEquatable<T>. Типизированный Equals позволяет избежать приведения типов, а значение null для ссылочного параметра обрабатывается явно.
using System;
public class Person : IEquatable<Person>
{
public string Name;
public Person(string name) { Name = name; }
public bool Equals(Person other) => other != null && other.Name == Name;
public override bool Equals(object obj) => Equals(obj as Person);
public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Person("Ada").Equals(new Person("Ada")));
Console.WriteLine(new Person("Ada").Equals((Person)null));
}
}Направляйте object.Equals через типизированный метод
Удобный шаблон — выполнить сравнение один раз в Equals(T), а object.Equals направить через него. Это гарантирует одинаковое поведение обоих методов.
using System;
public struct Id : IEquatable<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 class Program
{
public static void Main()
{
object boxed = new Id(5);
Console.WriteLine(boxed.Equals(new Id(5)));
}
}Сравнение нескольких полей
Для типов с несколькими полями сравнивайте их все в Equals. Два экземпляра равны только в том случае, если совпадает каждое значимое поле.
using System;
public struct DateRange : IEquatable<DateRange>
{
public int Start, End;
public DateRange(int start, int end) { Start = start; End = end; }
public bool Equals(DateRange other) => Start == other.Start && End == other.End;
public override bool Equals(object obj) => obj is DateRange d && Equals(d);
public override int GetHashCode() => HashCode.Combine(Start, End);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new DateRange(1, 5).Equals(new DateRange(1, 5)));
Console.WriteLine(new DateRange(1, 5).Equals(new DateRange(1, 6)));
}
}Сочетайте с операторами
Сочетайте IEquatable<T> с == и !=, чтобы пользователи могли сравнивать значения любым синтаксисом, опирающимся на одно и то же типизированное сравнение.
using System;
public struct Money : IEquatable<Money>
{
public decimal Amount;
public Money(decimal a) { Amount = a; }
public bool Equals(Money other) => Amount == other.Amount;
public override bool Equals(object obj) => obj is Money m && Equals(m);
public override int GetHashCode() => Amount.GetHashCode();
public static bool operator ==(Money a, Money b) => a.Equals(b);
public static bool operator !=(Money a, Money b) => !a.Equals(b);
}
public class Program
{
public static void Main()
{
Console.WriteLine(new Money(9.99m) == new Money(9.99m));
}
}Записи реализуют это автоматически
Запись C# record автоматически создаёт для вас IEquatable<T>, основанный на значениях Equals и GetHashCode. Если вам достаточно равенства по значению, запись избавляет от шаблонного кода.
using System;
public record Point(int X, int Y);
public class Program
{
public static void Main()
{
var a = new Point(1, 2);
var b = new Point(1, 2);
Console.WriteLine(a.Equals(b));
Console.WriteLine(a == b);
}
}Когда реализовывать вручную
Реализуйте IEquatable<T> вручную, когда нужны особые правила равенства, например сравнение имён без учёта регистра или игнорирование определённых полей, которые запись не может выразить напрямую.
using System;
public class CaseInsensitiveTag : IEquatable<CaseInsensitiveTag>
{
public string Name;
public CaseInsensitiveTag(string name) { Name = name; }
public bool Equals(CaseInsensitiveTag other)
=> other != null && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase);
public override bool Equals(object obj) => Equals(obj as CaseInsensitiveTag);
public override int GetHashCode() => Name?.ToLowerInvariant().GetHashCode() ?? 0;
}
public class Program
{
public static void Main()
{
Console.WriteLine(new CaseInsensitiveTag("Red").Equals(new CaseInsensitiveTag("RED")));
}
}Попробуйте сами
Реализуйте IEquatable<T> для небольшого ссылочного типа и направьте все способы проверки равенства через типизированный метод.
using System;
using System.Collections.Generic;
public class Coordinate : IEquatable<Coordinate>
{
public int Lat, Lng;
public Coordinate(int lat, int lng) { Lat = lat; Lng = lng; }
public bool Equals(Coordinate other) => other != null && other.Lat == Lat && other.Lng == Lng;
public override bool Equals(object obj) => Equals(obj as Coordinate);
public override int GetHashCode() => HashCode.Combine(Lat, Lng);
}
public class Program
{
public static void Main()
{
var set = new HashSet<Coordinate> { new Coordinate(10, 20) };
Console.WriteLine(set.Contains(new Coordinate(10, 20)));
}
}Быстрая проверка
Вспомните преимущество IEquatable
Итоги
IEquatable<T> обеспечивает безопасную проверку равенства с учётом типов.
- Предоставляет
Equals(T other), позволяя избежать упаковки для структур и приведения типов для классов. - Коллекции предпочитают этот интерфейс для корректного и эффективного поиска.
- Направляйте
object.Equalsи операторы через типизированный метод. - Записи реализуют его автоматически, а вручную его следует реализовывать для особых правил.
Часто задаваемые вопросы
Урок «Реализация IEquatable» бесплатный?
Да — полный текст урока «Реализация IEquatable» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.
Чему я научусь в уроке «Реализация IEquatable»?
Обеспечьте типобезопасное сравнение на равенство с помощью Equals. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать C# Academy?
Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Реализация IEquatable»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке C# Academy?
Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Реализация IEquatable
- Переопределение GetHashCode
- Реализация IComparable
- IComparer для пользовательской сортировки