Чем отличаются оператор == и вызов метода object.Equals в C#?
Ответы
Ответ 1
На самом деле, существует целый зоопарк методов сравнения объектов в C#.
Сравнение через ==. Это сравнение разрешается на этапе компиляции согласно заявленны
типам левой и правой части сравнения. Оператор сравнения можно перегрузить. Он является статическим, настоящий тип объектов не играет роли при выборе того, какой из перегруженных операторов сравнения будет использован. Пример:
object o1 = 5, o2 = 5;
bool eq = (o1 == o2); // false
Неперегруженный оператор == проверяет для ссылочных типов равенство ссылок. Допускаетс
значение null, null равен только другому null. Строка имеет перегруженный оператор ==, который сравнивает не ссылки, а содержимое строк. В перегруженном операторе для вашего объекта вы можете, разумеется, реализовать любую логику.
Оператор == для предопределённых типов-значений проверяет равенство этих значений
Для чисел, например, это может привести к приведению типов: если один из операндов int
а второй — double, то первый операнд тоже будет приведён к типу double (то есть 5 == 5.0 даёт true). Оператор == для пользовательских структур не определён, вы должны написать его сами.
Наличие статического оператора == для текущей версии языка не выражается при помощ
генерик-ограничения, поэтому этот оператор часто бесполезен в обобщённых методах для аргументов обобщённого типа.
Сравнение через виртуальный метод Equals(object). Это сравнение разрешается вызово
виртуальной функции Equals и основывается на динамическом типе левого аргумента. Пример:
object o1 = 5, o2 = 5;
bool eq = o1.Equals(o2); // true
Для непереопределённой функции, для ссылочных типов проверяется также равенство ссылок
В отличие от предыдущего случая, для вызова a.Equals(b) значение null в a, как и во всяком вызове метода, приводит к NullReferenceException.
ValueType, базовый класс всех типов-значений, переопределяет метод Equals. Таки
образом, если вы не переопределите этот метод, для типов-значений он будет вести себя следующим образом:
если все нестатические поля (включая приватные) типа-значения являются также типами-значениям
рекурсивно вниз (то есть, ни одно из полей и подполей не является ссылочным типом), то значения сравниваются побитно.
в противном случае применяется рефлексия, и значения всех полей сравниваются попарно через Equals(object).*
Затем, сравнение через рефлексию — штука медленная, поэтому если ваш тип-значение будет часто сравниваться, имеет смысл переопределить метод Equals(object).
Кроме того, сравнение через Equals(object) не приводит к преобразованиям типов, поэтому 5.Equals(5.0) даст false.
Если вы переопределяете Equals(object), скорее всего вам понадобится переопределить и GetHashCode() (о чём вам любезно напомнит компилятор).
Сравнение через object.Equals(object o1, object o2). Это по сути удобная обёртк
над Equals(object): метод проверяет, не пришёл ли к нему на вход один и тот же объек
или null, и только если обе эти проверки не дали результат, вызывает Equals(object) у первого объекта. Имеет смысл использовать этот метод вместо Equals(object), чтобы избежать обвязочного кода с проверками на null.
Следующий метод — реализация интерфейса IEquatable. Это типизированный интерфейс
так что вам не придётся проверять, какого типа ваш операнд. Как и Equals(object), эт
виртуальный метод. Впрочем, он не особо осмыслен в сложных иерархиях классов, поэтому скорее всего будет вызываться с известным runtime-типом обоих аргументов — ведь тип правой части задан параметром генерика!
Этот метод предпочтительнее для использования, чем перегрузки Equals(object), та
как у вас отпадают расходы на проверку типов и упаковку [boxing] (для типов-значений). Но если вы уж реализовали этот интерфейс, имеет смысл реализовать и Equals(object) совместимым образом:
// для класса (по контракту, Equals(null) должно возвращать false)
public override bool Equals(object o) => Equals(o as T);
// для структуры
public override bool Equals(object o) => o is T t && Equals(t);
Затем, если вам нужен специальный метод сравнения объектов для конкретной, а не обще
ситуации (или вы хотите сравнивать специальным образом объекты, которые вам не принадлежат)
вы можете делегировать сравнение специальному объекту, реализующему интерфейс IEqualityComparer (нетипизированный) или IEqualityComparer (типизированный). Сравнение при помощи таких сравнивающих объектов применяют, например, Hashatable и Dictionary, а также некоторые LINQ-методы.
В дополнение к нему есть вспомогательный класс EqualityComparer. EqualityComparer, который проверяет, реализует ли тип T интерфей
IEquatable, и в противном случае выполняет сравнение через Equal(object) (который всегда присутствует). (Также для реализации IEqualityComparer советуют наследоваться от EqualityComparer.)
И наконец, равенство/неравенство может быть получено из более общего сравнения н
больше/меньше/равно. Для этого используются операторы > (которые аналогичны оператор
==, но определены для более узкой группы типов), интерфейсы IComparable (аналог метода Equals(object)), IComparable (аналог интерфейса IEquatable), IComparer (аналог IEqualityComparer) и IComparer (аналог IEqualityComparer). А также делегат Comparison (например, для перегрузки List.Sort(Comparison)).
Переопределяя какой-либо из методов сравнения объектов, не забывайте, что ваш мето
должен быть рефлексивен, симметричен и транзитивен, а также по возможности не выбрасывать исключений (например, если операнды несовместимых типов, просто верните false).
*Из правила сравнения для ValueType следует, например, следующая тонкость. Побитово
равенство и равенство значений — немного разные вещи. Например, минус ноль не равен побитово плюс нулю. Поэтому такой код:
struct BitComparable
{
double d;
public BitComparable(double d) { this.d = d; }
}
struct NonBitComparable
{
double d; object o;
public NonBitComparable(double d, object o) { this.d = d; this.o = o; }
}
var x1 = new BitComparable(1 / double.PositiveInfinity); // +0
var y1 = new BitComparable(1 / double.NegativeInfinity); // -0
Console.WriteLine(x1.Equals(y1));
var x2 = new NonBitComparable(1 / double.PositiveInfinity, null);
var y2 = new NonBitComparable(1 / double.NegativeInfinity, null);
Console.WriteLine(x2.Equals(y2));
выводит False и True соответственно.
Ответ 2
Для значимых типов как оператор ==, так и метод Equals, проверяют равенство дву
значений.
Для ссылочных типов оператор == проверяет равенство ссылок (оба объекта должны указыват
на одно значение), а метод Equals проверяет равенство значений (оба объекта должны указыват
на равное значение). Исключения: для строк оператор == проверяет равенство значений, для классов, унаследованных от System.Object и не переопределивших метод Equals, метод Equals проверяет равенство ссылок.
Ответ 3
На самом деле, это исключение объясняется тем, что == является перегружаемым оператором
Перегрузить его так, чтобы осуществлялось сравнение значений вместо сравнения ссылок, можно в любом классе либо структуре. В структурах это обычно делается для обеспечения лучшей производительности по сравнению со стандартной реализацией.
Ответ 4
Про структуры
Дизайн паттерны рекомендуют реализовывать интерфейс IEquitable для структур,
соответственно, переопределяя метод Equals, GetHashCode - что позволяет существенно увеличить производительность, если вы работаете с большими объёмами данных.
Ибо операторы сравнения структур вызывают операции BOXING(UNBOXING), простыми словами обёртка, аля (object)"asdasd", что не есть хорошо.
Комментариев нет:
Отправить комментарий