В чем разница между двойными?а инт?для сравнения. - PullRequest
0 голосов
/ 26 сентября 2018

У меня очень странная ситуация, которую я не понимаю.Ниже упрощенный случай:

double? d = 2;
int? i = 2;

Console.WriteLine(d.Equals((2))); // false
Console.WriteLine(i.Equals((2))); // true

Я не понимаю, почему одно выражение даст мне истину, а другое - ложь.Они кажутся идентичными.

Ответы [ 2 ]

0 голосов
/ 26 сентября 2018

Похоже, что ответ в источнике для метода Equals для каждого из типов.Если типы не совпадают, то они не равны.

https://referencesource.microsoft.com/#mscorlib/system/double.cs,147

// True if obj is another Double with the same value as the current instance.  This is
// a method of object equality, that only returns true if obj is also a double.
public override bool Equals(Object obj) {
    if (!(obj is Double)) {
        return false;
    }
    double temp = ((Double)obj).m_value;
    // This code below is written this way for performance reasons i.e the != and == check is intentional.
    if (temp == m_value) {
        return true;
    }
    return IsNaN(temp) && IsNaN(m_value);
}

https://referencesource.microsoft.com/#mscorlib/system/int32.cs,72

public override bool Equals(Object obj) {
    if (!(obj is Int32)) {
        return false;
    }
    return m_value == ((Int32)obj).m_value;
}
0 голосов
/ 26 сентября 2018

Вы совершенно правы, находя это в замешательстве.Это беспорядок.

Давайте начнем с того, что четко расскажем о том, что происходит, посмотрев больше примеров, а затем выведем правильные правила, которые здесь применяются.Давайте расширим вашу программу, чтобы рассмотреть все эти случаи:

    double d = 2;
    double? nd = d;
    int i = 2;
    int? ni = i;
    Console.WriteLine(d == d);
    Console.WriteLine(d == nd);
    Console.WriteLine(d == i);
    Console.WriteLine(d == ni);
    Console.WriteLine(nd == d);
    Console.WriteLine(nd == nd);
    Console.WriteLine(nd == i);
    Console.WriteLine(nd == ni);
    Console.WriteLine(i == d);
    Console.WriteLine(i == nd);
    Console.WriteLine(i == i);
    Console.WriteLine(i == ni);
    Console.WriteLine(ni == d);
    Console.WriteLine(ni == nd);
    Console.WriteLine(ni == i);
    Console.WriteLine(ni == ni);
    Console.WriteLine(d.Equals(d));
    Console.WriteLine(d.Equals(nd));
    Console.WriteLine(d.Equals(i));
    Console.WriteLine(d.Equals(ni)); // False
    Console.WriteLine(nd.Equals(d));
    Console.WriteLine(nd.Equals(nd));
    Console.WriteLine(nd.Equals(i)); // False
    Console.WriteLine(nd.Equals(ni)); // False
    Console.WriteLine(i.Equals(d)); // False
    Console.WriteLine(i.Equals(nd)); // False
    Console.WriteLine(i.Equals(i)); 
    Console.WriteLine(i.Equals(ni));
    Console.WriteLine(ni.Equals(d)); // False
    Console.WriteLine(ni.Equals(nd)); // False
    Console.WriteLine(ni.Equals(i)); 
    Console.WriteLine(ni.Equals(ni));

Все из них печатают True, кроме тех, которые я записал как печать false.

Сейчас я дам анализ этих случаев.

Первое, на что нужно обратить внимание, это то, что оператор == всегда говорит True.Почему это так?

Семантика ненулевого значения == такова:

int == int -- compare the integers
int == double -- convert the int to double, compare the doubles
double == int -- same
double == double -- compare the doubles

Таким образом, в каждом ненулевом случае целое число 2 равно двойному 2,0, потому что int2 преобразуется в удвоенный 2,0, и сравнение верно.

Семантика обнуляемого значения ==:

  • Если оба операнда равны нулю, они равны
  • Если один равен нулю, а другой - нет, они неравны
  • Если оба не равны нулю, вернитесь к ненулевому случаю, приведенному выше.

Итакопять же, мы видим, что для сравнений, допускающих обнуляемость, int? == double?, int? == double и т. д. мы всегда возвращаемся к ненулевым значениям, преобразуем int? в double и выполняем сравнение в двойных числах.Таким образом, все это также верно.

Теперь мы подошли к Equals, где все запуталось.

Здесь есть фундаментальная проблема проектирования, о которой я писал в 2009 году:https://blogs.msdn.microsoft.com/ericlippert/2009/04/09/double-your-dispatch-double-your-fun/ - проблема в том, что значение == разрешается на основе типов времени компиляции обоих операндов .Но Equals разрешается на основе типа времени выполнения операнда left (получателя), но типа времени компиляции право операнд (аргумент), и именно поэтому все идет не так, как надо.

Давайте начнем с рассмотрения того, что делает double.Equals(object).Если получатель вызова к Equals(object) равен double, тогда , если аргумент не является двойным числом, они считаются не равными .То есть Equals требует, чтобы типы соответствовали , тогда как == требует, чтобы типы были преобразованы в общий тип .

Я скажу это снова.double.Equals не не пытается преобразовать свой аргумент в double, в отличие от ==.Он просто проверяет, является ли он уже двойным, а если нет, то он говорит, что они не равны.

Это объясняет, почему d.Equals(i) ложно ... но... подождите минуту, это не ложь выше!Чем это объясняется?

double.Equals перегружен!Выше мы на самом деле вызываем double.Equals(double), который - как вы уже догадались - преобразует int в double перед выполнением вызова!Если бы мы сказали d.Equals((object)i)), тогда это было бы неверно.

Хорошо, поэтому мы знаем, почему double.Equals(int) - правда, потому что int преобразуется в double.

Мы также знаемпочему double.Equals(int?) ложно.int? не конвертируется в удвоение, но в object.Итак, мы называем double.Equals(object) и ставим int, и теперь оно не равно.

Что насчет nd.Equals(object)?Семантика этого:

  • Если получатель равен нулю, а аргумент равен нулю, они равны
  • Если получатель не равен нулю, тогда перейдите к ненулевой семантикеd.Equals(object)

Итак, теперь мы знаем, почему nd.Equals(x) работает, если x является double или double?, но не если это int или int?.(Хотя интересно, конечно, (default(double?)).Equals(default(int?)) будет правдой, так как они оба равны нулю!)

Наконец, с помощью аналогичной логики мы видим, почему int.Equals(object) дает такое поведение.Он проверяет, является ли его аргумент упакованным int, а если нет, то возвращает false.Таким образом, i.Equals(d) является ложным.i нельзя преобразовать в удвоение, а d нельзя преобразовать в целое число.

Это огромный беспорядок .Нам бы хотелось, чтобы равенство было отношением эквивалентности , и это не так!Соотношение равенства должно иметь следующие свойства:

  • Рефлексивность: вещь равна себе.Это обычно true в C #, хотя есть несколько исключений.
  • Симметрия: если A равно B, то B равно A. Это верно для == в C #, ноне A.Equals(B), как мы видели.
  • Транзитивность: если A равен B, а B равен C, то A также равен C. Это совершенно не так в C #.

Итак, беспорядок на всех уровнях.== и Equals имеют разные механизмы диспетчеризации и дают разные результаты, ни один из них не является отношением эквивалентности, и это все время сбивает с толку.Извиняюсь за то, что втянул вас в этот беспорядок, но когда я приехал, это был беспорядок.

Несколько иное мнение о том, почему равенство ужасно в C #, см. В пункте номер девять в моем списке достойных сожаления языковых решений, здесь:http://www.informit.com/articles/article.aspx?p=2425867

БОНУСНОЕ УПРАЖНЕНИЕ: повторите вышеупомянутый анализ, но для x?.Equals(y) для случаев, когда x обнуляется.Когда вы получаете те же результаты, что и для не обнуляемых приемников, и когда вы получаете другие результаты?

...