В чем разница между == и Equals () для примитивов в C #?


180

Рассмотрим этот код:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Оба intи shortявляются примитивными типами, но сравнение с ==возвращает true, а сравнение с Equalsвозвращает false.

Зачем?


9
@OrangeDog Пожалуйста, подумайте над вопросом, а затем проголосуйте за закрытие

4
Здесь отсутствует очевидная обратная попытка:Console.WriteLine(age.Equals(newAge));
АНВС считает SE злом

3
Дубликат не объясняет это поведение; это как раз о том, что Equals()есть в целом.
SLaks

37
Я ответил на этот точный вопрос в блоге Coverity несколько дней назад. blog.coverity.com/2014/01/13/inconsistent-equality
Эрик Липперт,

5
@CodesInChaos: В спецификации фактически дважды используется термин «примитивные типы» без его определения; подразумевается, что примитивные типы являются встроенными типами значений, но это никогда не проясняется. Я рекомендовал Мэдсу просто исключить этот термин из спецификации, поскольку он, кажется, создает больше путаницы, чем устраняет.
Эрик Липперт

Ответы:


262

Короткий ответ:

Равенство сложно.

Подробный ответ:

Типы примитивов переопределяют базовые object.Equals(object)и возвращают истину, если упакованные в штучную упаковку objectимеют тот же тип и значение. (Обратите внимание, что это также будет работать для типов, допускающих значение NULL; типы, не допускающие значения NULL, всегда передаются экземпляру базового типа.)

Поскольку newAgeэто a short, его Equals(object)метод возвращает true только в том случае, если вы передаете короткую коробку с тем же значением. Вы передаете коробку int, поэтому она возвращает false.

Напротив, ==оператор определяется как принимающий два ints (или shorts, или longs).
Когда вы вызываете его с intи short, компилятор неявно преобразовать shortв intи сравнить полученный ints по значению.

Другие способы заставить это работать

Примитивные типы также имеют свой собственный Equals()метод, который принимает тот же тип.
Если вы напишете age.Equals(newAge), компилятор выберет int.Equals(int)лучшую перегрузку и неявно преобразуетshort в int. Затем он вернется true, поскольку этот метод просто сравнивает ints напрямую.

short также есть short.Equals(short) метод, но intне может быть неявно преобразован в short, поэтому вы его не вызываете.

Вы можете заставить его вызвать этот метод с помощью приведения:

Console.WriteLine(newAge.Equals((short)age)); // true

Это вызовет short.Equals(short) напрямую, без бокса. Если ageбольше 32767, будет выдано исключение переполнения.

Вы также можете позвонить в short.Equals(object) перегрузку, но явно передать упакованный объект, чтобы он получил тот же тип:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Как и предыдущая альтернатива, это вызовет переполнение, если оно не помещается в short . В отличие от предыдущего решения, оно помещает shortобъект в объект, тратя время и память.

Исходный код:

Вот оба Equals()метода из реального исходного кода:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Дальнейшее чтение:

См. Эрика Липперта .


3
@SLaks, если называть long == int, intнеявно преобразован в longправый?
Selman Genç

1
И да, я написал все это, даже не пробуя.
SLaks

1
Помните, что в коде вопроса, если кто-то изменится int age = 25;на const int age = 25;, то результат изменится. Это происходит потому , что неявное преобразование intв shortне существует в этом случае. См. Неявные преобразования константных выражений .
Jeppe Stig Nielsen

2
@SLaks да, но формулировка вашего ответа «переданное значение» может интерпретироваться обоими способами (как значение, переданное разработчиком, или значение, которое фактически передается CLR после распаковки). Я предполагаю, что случайный пользователь, который еще не знает ответов здесь, прочитает это как прежнее
JaredPar

2
@ Рэйчел: Вот только это неправда; оператор по умолчанию == сравнивает ссылочные типы по ссылке. Для типов значений и для типов, которые перегружают ==, это не так.
SLaks

55

Потому что нет перегрузки для того, short.Equalsчтобы принимать int. Поэтому это называется:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

objэто не short.. следовательно, это ложь.


12

Когда вы переходите intк shortEquals, вы проходите object:

введите описание изображения здесь Итак, этот псевдокод работает:

return obj is short && this == (short)obj;


10

==используется для проверки условия равенства, его можно рассматривать как оператор (логический оператор), просто чтобы сравнить 2 вещи, и здесь тип данных не имеет значения, поскольку будет выполнено приведение типа, а Equalsтакже используется для проверки условия равенства , но в этом случае типы данных должны быть одинаковыми. N Equals - это метод, а не оператор.

Ниже приведен небольшой пример, взятый из того, который вы предоставили, и он вкратце разъяснит разницу.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

в приведенном выше примере X и Y имеют одинаковые значения, то есть 1, и когда мы используем ==, он вернет истину, как в случае== , короткий тип преобразуется компилятором в int и выдается результат.

и когда мы используем Equals, сравнение выполняется, но преобразование типов не выполняется компилятором, поэтому возвращается false.

Ребята, дайте мне знать, если я ошибаюсь.


6

Во многих контекстах, где аргумент метода или оператора не относится к требуемому типу, компилятор C # попытается выполнить неявное преобразование типа. Если компилятор может заставить все аргументы удовлетворять свои операторы и методы, добавляя неявные преобразования, он сделает это без жалоб, даже если в некоторых случаях (особенно с тестами на равенство!) Результаты могут быть неожиданными.

Кроме того, каждый тип значения, такой как intили shortфактически, описывает как вид значения, так и вид объекта (*). Неявные преобразования существуют для преобразования значений в другие типы значений и преобразования любого значения в соответствующий ему вид объекта, но различные типы объектов не могут быть неявно преобразованы друг в друга.

Если использовать ==оператор для сравнения a shortи an int, shortбудет неявно преобразован в int. Если его числовое значение было равно значению, то значение int, в intкоторое оно было преобразовано, будет равно значению, intс которым оно сравнивается. Однако, если кто-то попытается использовать Equalsметод на коротком отрезке, чтобы сравнить его с объектом int, единственным неявным преобразованием, которое удовлетворяет перегрузке Equalsметода, будет преобразование в тип объекта, соответствующий int. Когда его shortспросят, соответствует ли он переданному объекту, он заметит, что рассматриваемый объект является, intа неshort и, таким образом выводу, что он не может быть равен.

В общем, хотя компилятор не будет жаловаться на это, следует избегать сравнения вещей, которые не относятся к одному типу; если кто-то интересуется, приведет ли преобразование вещей к общей форме к тому же результату, следует выполнить такое преобразование явно. Рассмотрим, например,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

Есть три способа сравнить a intс a float. Возможно, вы захотите узнать:

  1. Есть ли наиболее возможное floatзначение к intсопрягать float?
  2. Имеет ли весь номер частью floatсопрягать int?
  3. Сделайте intи floatпредставляют одно и то же числовое значение.

Если попытаться сравнить intи floatнапрямую, скомпилированный код ответит на первый вопрос; Однако далеко не очевидно, что задумал программист. Изменение сравнения на (float)i == fпрояснит, что было предназначено первое значение, или (double)i == (double)fзаставит код ответить на третий вопрос (и прояснить, что было задумано).

(*) Даже если спецификация C # рассматривает значение типа, например, System.Int32как объект типа System.Int32, такое представление противоречит требованию, чтобы код выполнялся на платформе, спецификация которой рассматривает значения и объекты как населяющие разные вселенные. Кроме того, если Tэто ссылочный тип, а x- это T, то ссылка типа Tдолжна иметь возможность ссылаться на x. Таким образом, если переменная vтипа Int32содержит Object, ссылка типа Objectдолжна иметь возможность содержать ссылку vили ее содержимое. Фактически, ссылка типа Objectможет указывать на объект, содержащий скопированные данные, и его содержимое на самом деле не является .v , но не на vсебя или его содержимое. Это предполагает, что ниvObject


1
the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to intНеправильно. В отличие от Java, в C # нет отдельных примитивных и упакованных типов. Его помещают в коробку, objectпотому что это единственная другая перегрузка Equals().
SLaks

Первый и третий вопрос идентичны; точное значение уже было потеряно при преобразовании в float. Преобразование a floatв a doubleне создаст новой точности волшебным образом.
SLaks

@SLaks: согласно спецификации ECMA, описывающей виртуальную машину, на которой работает C #, каждое определение типа значения создает два различных типа. В спецификации C # может быть сказано, что содержимое места хранения типа List<String>.Enumeratorи объекта кучи типа List<String>.Enumeratorодинаково, но в спецификации ECMA / CLI говорится, что они разные, и даже при использовании в C # они ведут себя по-разному.
supercat

@SLaks: Если бы iи fбыли преобразованы в каждый doubleдо сравнения, они дали бы 16777217.0 и 16777216.0, которые сравниваются как неравные. Преобразование i floatдаст 16777216.0f, по сравнению с f.
supercat

@SLaks: Для простого примера разницы между типами места хранения и типами объектов в штучной упаковке рассмотрим этот метод bool SelfSame<T>(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}. Упакованный тип объекта, соответствующий типу значения, может удовлетворять типу параметра ReferenceEqualsпосредством восходящего преобразования с сохранением идентичности; однако тип места хранения требует преобразования без сохранения идентичности . Если приведение Tк Uдает ссылку на что-то другое, кроме оригинала T, это подсказывает мне, что a на Tсамом деле не является U.
supercat

5

Равно () представляет собой метод System.Object класса
Синтаксиса: Общественное Equals виртуальной BOOL ()
Рекомендация , если мы хотим , чтобы сравнить состояние двух объектов , то мы должны использовать Equals () метод

как указано выше, операторы ответов == сравнивают значения одинаковы.

Пожалуйста, не путайте с ReferenceEqual

Ссылка Equals ()
Синтаксис: public static bool ReferenceEquals ()
Определяет, принадлежат ли указанные экземпляры объектов к одному экземпляру.


8
Это вообще не отвечает на вопрос.
SLaks

SLaks, который я не объяснил на примерах, это основа вышеприведенного вопроса.
Sugat Mankar

4

Что вам нужно понять, так это то, что выполнение ==всегда приводит к вызову метода. Вопрос в том, звонит ли ==и в Equalsконце концов звонит / делает то же самое.

Со ссылочными типами ==всегда сначала проверяет, совпадают ли ссылки ( Object.ReferenceEquals). Equalsс другой стороны, его можно переопределить и проверить, равны ли некоторые значения.

РЕДАКТИРОВАТЬ: чтобы ответить на svick и добавить комментарий SLaks, вот некоторый код IL

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.

Итак, какой метод сравнивает два ints с вызовом ==? Подсказка: нет operator ==метода для Int32, но есть дляString .
svick

2
Это вообще не отвечает на вопрос.
SLaks

@SLaks: он действительно не отвечает на конкретный вопрос о int и коротком сравнении, вы уже ответили на него. Мне все еще кажется интересным объяснить, что ==это не просто волшебство, а просто вызов метода (большинство программистов, вероятно, никогда не реализовали / не переопределили какой-либо оператор). Возможно, я мог бы добавить комментарий к вашему вопросу вместо того, чтобы добавлять свой ответ. Не стесняйтесь обновлять свою, если считаете, что то, что я сказал, актуально.
user276648

Обратите внимание, что ==для примитивных типов это не перегруженный оператор, а встроенная функция языка, которая компилируется в ceqинструкцию IL.
SLaks

3

== В примитивном

Console.WriteLine(age == newAge);          // true

В примитивном сравнении оператор == ведет себя совершенно очевидно. В C # доступно множество перегрузок оператора ==.

  • строка == строка
  • int == int
  • uint == uint
  • длинный == длинный
  • многое другое

Итак, в этом случае неявное преобразование из int к shortа shortк intвозможно. Таким образом, newAge преобразуется в int, и происходит сравнение, которое возвращает true, поскольку оба имеют одинаковое значение. Так что это эквивалентно:

Console.WriteLine(age == (int)newAge);          // true

.Equals () в примитиве

Console.WriteLine(newAge.Equals(age));         //false

Здесь нам нужно увидеть, что такое метод Equals (), мы вызываем Equals с переменной типа short. Итак, есть три возможности:

  • Equals (object, object) // статический метод из объекта
  • Equals (object) // виртуальный метод из объекта
  • Equals (короткий) // Реализует IEquatable.Equals (короткий)

Первый тип здесь не используется, так как количество аргументов отличается, мы вызываем только один аргумент типа int. Третье также исключено, поскольку неявное преобразование int в short невозможно. Так здесь второй тип Equals(object)называется. Вshort.Equals(object) :

bool Equals(object z)
{
  return z is short && (short)z == this;
}

Итак, вот условие было проверено z is short которое является ложным, поскольку z является int, поэтому оно возвращает false.

Вот подробная статья Эрика Липперта.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.