Вы совершенно правы, находя это в замешательстве.Это беспорядок.
Давайте начнем с того, что четко расскажем о том, что происходит, посмотрев больше примеров, а затем выведем правильные правила, которые здесь применяются.Давайте расширим вашу программу, чтобы рассмотреть все эти случаи:
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
обнуляется.Когда вы получаете те же результаты, что и для не обнуляемых приемников, и когда вы получаете другие результаты?