Подтвердить что ты не робот

Почему целочисленный ноль не равен длинному нулю?

Странный фрагмент кода, который я только что открыл в С# (также должен быть прав для других языков CLI, используя .NET structs).

using System;

public class Program
{
    public static void Main(string[] args)
    {
    int a;
    long b;

    a = 0;
    b = 0;

    Console.WriteLine(a.Equals(b)); // False
    Console.WriteLine(a.Equals(0L)); // False
    Console.WriteLine(a.Equals((long)0)); // False
    Console.WriteLine(a.Equals(0)); // True
    Console.WriteLine(a.Equals(a)); // True
    Console.WriteLine(a == b); // True
    Console.WriteLine(a == 0L); // True

    Console.WriteLine();

    Console.WriteLine(b.Equals(a)); // True
    Console.WriteLine(b.Equals(0)); // True
    Console.WriteLine(b.Equals((int)0)); // True
    Console.WriteLine(b.Equals(b)); // True
    Console.WriteLine(b == a); // True
    Console.WriteLine(b == 0); // True
    }
}

Здесь два интересных момента (при условии, что a есть int и b есть long):

  • a != b, но b == a;
  • (a.Equals(b)) != (a == b)

Есть ли причина, по которой сравнение было реализовано таким образом?

Примечание:.NET 4 использовался, если это имеет значение.

4b9b3361

Ответ 1

В общем случае методы Equals() не должны возвращать true для объектов разных типов.

a.Equals(b) вызывает int.Equals(object), который может возвращать true только для вложенных пакетов Int32 s:

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

b.Equals(a) вызывает long.Equals(long) после неявного преобразования int в long.
Поэтому он сравнивает два long напрямую, возвращая true.

Чтобы понять более четко, посмотрите на IL, сгенерированный этим более простым примером (который печатает True False True):

int a = 0;
long b = 0L;

Console.WriteLine(a == b);
Console.WriteLine(a.Equals(b));
Console.WriteLine(b.Equals(a));

IL_0000:  ldc.i4.0    
IL_0001:  stloc.0     
IL_0002:  ldc.i4.0    
IL_0003:  conv.i8     
IL_0004:  stloc.1     

IL_0005:  ldloc.0     //Load a
IL_0006:  conv.i8     //Cast to long
IL_0007:  ldloc.1     //Load b
IL_0008:  ceq         //Native long equality check
IL_000A:  call        System.Console.WriteLine    //True

IL_000F:  ldloca.s    00            //Load the address of a to call a method on it
IL_0011:  ldloc.1                   //Load b
IL_0012:  box         System.Int64  //Box b to an Int64 Reference
IL_0017:  call        System.Int32.Equals
IL_001C:  call        System.Console.WriteLine    //False

IL_0021:  ldloca.s    01  //Load the address of b to call a method on it
IL_0023:  ldloc.0         //Load a
IL_0024:  conv.i8         //Convert a to Int64
IL_0025:  call        System.Int64.Equals
IL_002A:  call        System.Console.WriteLine    //True

Ответ 2

Они не совпадают, потому что даже простые типы наследуются от System.Object - они фактически являются объектами, а разные типы объектов, даже с одинаковыми значениями свойств, не равны.

Пример:

У вас может быть объект Co-Worker с одним свойством: Name (string) и партнерский объект только с одним свойством: Name (string)

Сотрудник Дэвид не такой же, как Парнер Дэвид. Тот факт, что они представляют собой разные типы объектов, отличает их друг от друга.

В вашем случае, используя .Equals(), вы не сравниваете значения, вы сравниваете объекты. Объект не "0" - это System.Int32 со значением нуля и System.Int64 со значением нуля.

Пример кода, основанный на вопросе в комментарии ниже:

class CoWorker
{
   public string Name { get; set; }
}

class Partner
{
   public string Name { get; set; }
}

private void button1_Click(object sender, RoutedEventArgs e)
{
   CoWorker cw = new CoWorker();
   cw.Name = "David Stratton";
   Partner p = new Partner();
   p.Name = "David Stratton";

   label1.Content = cw.Equals(p).ToString();  // sets the Content to "false"
}

Ответ 3

Существует также проблема сужения или расширения конверсии. Ноль long всегда равен нулю int, но не наоборот.

Когда длинный сравнивается с int, сравниваются только наименее значимые 32 бита, а остальные игнорируются, поэтому операция int.Equals(long) не может гарантировать равенство, даже если младшие бит совпадают.

int a = 0;
long b = 0;

Trace.Assert(a.Equals((int)b));     // True   32bits compared to 32bits
Trace.Assert(a.Equals((long)b));    // False  32bits compared to 64bits (widening)
Trace.Assert(b.Equals((long)a));    // True   64bits compared to 64bits
Trace.Assert(b.Equals((int)a));     // True   64bits compared to 32bits (narrowing)

Также рассмотрим случай, когда нижние 32-биты равны, а верхние - нет.

uint a = 0;
ulong b = 0xFFFFFF000000;
Trace.Assert((uint)a == (uint)b);  // true because of a narrowing conversion
Trace.Assert((ulong)a == (ulong)b);  // false because of a widening conversion

Ответ 4

Перегрузки операторов и методов, а также операторы преобразования оцениваются во время компиляции, в отличие от переопределений виртуальных методов, которые оцениваются во время выполнения. Выражение someIntVar.Equals(someNumericQuantity) полностью не связано с выражением someObjectVarThatHoldsAnInt.Equals(someNumericQuantity). Если бы вы притворились, что виртуальный метод Object.Equals имеет другое имя (например, IsEquivalentTo) и заменяет это имя на каждое место, где используется виртуальный метод, это было бы намного яснее. Integer нуль может быть численно равен длинному нолю, но это не значит, что они семантически эквивалентны.

Такое разделение по смыслу между Equals и IsEquivalentTo, кстати, также помогло бы избежать мутности в определении последнего. Можно определить значимое отношение эквивалентности для произвольных объектов: место хранения X должно считаться эквивалентным месту хранения Y, если поведение всех членов первого всегда будет эквивалентно соответствующим членам последнего, а единственный способ определить, относятся ли теги X и Y к одному и тому же объекту, используя Reflection или ReferenceEquals. Даже если 1.0m.Equals(1.00m) является и должен быть истинным, 1.0m.IsEquivalentTo(1.00m) должен быть ложным. К сожалению, использование одного и того же имени для метода проверки эквивалентности объекта и метода тестирования числового равенства Decimal привело Microsoft к тому, чтобы определить, как первое ведет себя как последнее.

Ответ 5

потому что Equals сравнивает объекты, а объекты a и b различаются. Они имеют одинаковое значение, но различаются как объекты

Эта ссылка может вам помочь: http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

Ответ 6

С# не выполняет автоматическое кастинг. Функция Equals сравнивает типы, а также значения. Очень похоже на === в JS.