0Pricing
C# Academy · Урок

Реализация 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 — локальная установка не требуется.

Все уроки этого курса

  1. Реализация IEquatable
  2. Переопределение GetHashCode
  3. Реализация IComparable
  4. IComparer для пользовательской сортировки
← Назад к C# Academy