Яка різниця між == та рівним () для примітивів у C #?


180

Розглянемо цей код:

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

Обидва intі shortє примітивними типами, але порівняння з ==істинними поверненнями і порівняння з Equalsповерненнями хибними.

Чому?


9
@OrangeDog Подумайте над питанням, а потім проголосуйте, щоб закрити

4
Тут відсутня очевидна зворотна спроба:Console.WriteLine(age.Equals(newAge));
ANeves

3
Дублікат не пояснює цю поведінку; мова йде лише про те, що Equals()взагалі є.
СЛАкс

37
Я відповів на це точне запитання в блозі Coverity кілька днів тому. blog.coverity.com/2014/01/13/inconsistent-equality
Ерік Ліпперт

5
@CodesInChaos: У специфікації фактично використовується термін "примітивні типи" двічі, не визначаючи його ніколи; сенс полягає в тому, що примітивні типи - це вбудовані типи значень, але це ніколи не стає зрозумілим. Я рекомендував Mads, щоб цей термін просто викреслити із специфікації, оскільки він, здається, створює більше плутанини, ніж видаляє.
Ерік Ліпперт

Відповіді:


262

Коротка відповідь:

Рівність є складною.

Детальна відповідь:

Типи примітивів перекривають основу object.Equals(object)і повертають істину, якщо бокс objectє одного типу та значення (Зверніть увагу, що він також буде працювати для змінних типів; ненульові зведені типи завжди вказують на екземпляр базового типу.)

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

На відміну від цього, ==оператор визначається як прийняття двох ints (або shorts або longs).
Коли ви називаєте це з a intі a short, компілятор неявно перетворить shortна intта порівняє отриманеint s за значенням.

Інші способи змусити його працювати

Примітивні типи також мають свій 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
І так, я все це написав, не намагаючись насправді.
СЛак

1
Пам'ятайте , що в коді питання, якщо один зміни int age = 25;в const int age = 25;, то результат зміниться. Це відбувається тому , що неявне перетворення intв shortне існує в цьому випадку. Див. Неявні перетворення постійних виразів .
Jeppe Stig Nielsen

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

2
@Rachel: За винятком того, що це неправда; оператор за замовчуванням == порівнює типи посилань за посиланням. Для типів значень і для типів, які перевантажують ==, це не так.
СЛАкс

55

Тому що немає перевантаження для short.Equalsцього, приймає int. Тому це називається:

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

objне є short.. отже, це помилково.


12

Коли ви переходите intдо short's дорівнює, ви переходите object:

введіть тут опис зображення Отже, цей псевдокод працює:

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


10

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

Нижче наведено невеликий приклад, взятий із запропонованого вами, і це коротко пояснить різницю.

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

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

і коли ми використовуємо Equals, порівняння робиться, але кастинг типу не робиться компілятором, тому повертається помилка.

Хлопці, будь ласка, повідомте мене, якщо я помиляюся.


6

У багатьох контекстах, де аргумент методу чи оператора не є потрібним типом, компілятор C # спробує здійснити неявне перетворення типу. Якщо компілятор може змусити всі аргументи задовольнити своїх операторів та методи, додавши неявні перетворення, він зробить це без скарги, хоча в деяких випадках (особливо з тестами рівності!) Результати можуть бути дивовижними.

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

Якщо використовується ==оператор для порівняння a shortі an int, то shortбуде неявно перетворено в an 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є a T, тоді посилання типу Tповинно бути здатне посилатися x. Таким чином, якщо змінна vтипу Int32має значення Object, посилання типу Objectповинна мати змогу містити посилання на vйого вміст. Фактично, посилання типу Objectмогла б вказувати на об'єкт, що містить дані, скопійовані з v, але не на vсебе, ані на його зміст. Це підказало б, що жоднеvні його зміст справді не є Object.


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().
СЛАкс

Перше і третє питання однакові; точне значення вже було втрачено при перетворенні в float. Відсилка floatдо doubleволі не магічно створить нову точність.
СЛАкс

@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, який запропонував би мені , що Tце на самому ділі не U.
supercat

5

Equals () - це метод синтаксису Class.Object Class
: Public virtual bool Equals ()
Рекомендація, якщо ми хочемо порівняти стан двох об'єктів, тоді ми повинні використовувати метод Equals ()

як зазначено вище відповіді == оператори порівнюють значення однакові.

Не плутайте з ReferenceEqual

Еталонні Рівно ()
Синтаксис: суспільне статичні BOOL ReferenceEquals ()
Це визначають , є чи зазначений примірник об'єкта одного і того ж примірника


8
Це зовсім не відповідає на питання.
СЛАкс

SLaks, які я пояснив на прикладах, це основні питання.
Сугат Манкар

4

Те, що вам потрібно усвідомити, - це те, що робити ==завжди викличе метод. Питання в тому, чи дзвонить ==і Equalsзакінчує дзвонити / робити те саме.

Що стосується типів посилань, ==завжди 1-а перевіряє, чи посилання однакові ( Object.ReferenceEquals). Equalsз іншого боку, це може бути відмінено і може перевірити, чи є деякі значення рівними.

EDIT: щоб відповісти 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: він дійсно не відповідає на конкретне запитання про int та коротке порівняння, ви вже відповіли на нього. Я все ще відчуваю, що цікаво пояснити, що ==це не просто магія, вона зрештою просто викликає метод (більшість програмістів, мабуть, ніколи не реалізовували / не перекривали жодного оператора). Можливо, я міг би додати коментар до вашого питання замість того, щоб додати свою власну відповідь. Не соромтеся оновлювати своє, якщо ви вважаєте, що я сказав, що це актуально.
користувач276648

Зауважте, що ==на примітивних типах - це не перевантажений оператор, а властива мовна функція, яка компілюється в ceqінструкцію IL.
СЛАкс

3

== У примітиві

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

У примітивному порівнянні == оператор поводиться досить очевидно, у C # є багато == перевантаження оператора.

  • рядок == рядок
  • int == int
  • uint == uint
  • довгий == довгий
  • набагато більше

Тому в цьому випадку не можливе неявне перетворення з intна, shortа shortв intможливе. Отже, newAge перетворюється в int і відбувається порівняння, яке повертає істину, оскільки обидва мають одне значення. Отже, це рівнозначно:

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

.Equals () у примітиві

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

Тут нам потрібно побачити, що таке метод Equals (), ми називаємо Equals з короткою змінною типу. Отже, є три можливості:

  • Дорівнює (об'єкт, об'єкт) // статичний метод від об'єкта
  • Дорівнює (об’єкт) // віртуальний метод від об'єкта
  • Дорівнює (короткий) // Реалізує IEquatable.Equals (короткий)

Першого типу тут немає, оскільки кількість аргументів відрізняється, ми викликаємо лише один аргумент типу int. Третє також усувається, як згадувалося вище, неявне перетворення int в коротке неможливо. Тому тут 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.