C#. Непоследовательное сравнение

в 9:00, , рубрики: .net, Программирование

C#. Непоследовательное сравнение
От переводчика:
Это вольный перевод блогозаписи Эрика Липперта (Eric Lippert), в прошлом одного из разработчиков языка C#. Запись оформлена в виде «вопрос-ответ», я пропущу вопрос и перейду к ответу, вы можете ознакомиться с вопросом в оригинале, но там ничего особо интересного.

Но, для начала, я попрошу взглянуть на следующий код, и без гугла и компилирования, попробовать выяснить что произойдет в 9 случаях сравнения и свериться с ответами (для опроса):

int myInt = 1;
short myShort = 1;
object objInt1 = myInt;
object objInt2 = myInt;
object objShort = myShort;
Console.WriteLine(myInt == myShort);          // scenario 1
Console.WriteLine(myShort == myInt);          // scenario 2
Console.WriteLine(myInt.Equals(myShort));     // scenario 3
Console.WriteLine(myShort.Equals(myInt));     // scenario 4
Console.WriteLine(objInt1 == objInt1);        // scenario 5
Console.WriteLine(objInt1 == objShort);       // scenario 6
Console.WriteLine(objInt1 == objInt2);        // scenario 7
Console.WriteLine(Equals(objInt1, objInt2));  // scenario 8
Console.WriteLine(Equals(objInt1, objShort)); // scenario 9



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

Напишем следующий код, чтобы проиллюстрировать различные степени сравнения.

Ответы

int myInt = 1;
short myShort = 1;
object objInt1 = myInt;
object objInt2 = myInt;
object objShort = myShort;
Console.WriteLine(myInt == myShort);          // scenario 1 true
Console.WriteLine(myShort == myInt);          // scenario 2 true
Console.WriteLine(myInt.Equals(myShort));     // scenario 3 true
Console.WriteLine(myShort.Equals(myInt));     // scenario 4 false!
Console.WriteLine(objInt1 == objInt1);        // scenario 5 true
Console.WriteLine(objInt1 == objShort);       // scenario 6 false!!
Console.WriteLine(objInt1 == objInt2);        // scenario 7 false!!!
Console.WriteLine(Equals(objInt1, objInt2));  // scenario 8 true
Console.WriteLine(Equals(objInt1, objShort)); // scenario 9 false!?!

Что за черт? Разберем все по-порядку.

В первом и втором случае, мы должны вначале определить что значит оператор ==. В C# существует более десятка встроенных операторов == для сравнения различных типов.

object == object
string == string
int == int
uint == uint
long == long
ulong == ulong
...

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

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

Equals(object, object) // статический метод унаследованный от object
Equals(object)         // виртуальный метод унаследованный от object
Equals(int)            // реализация метода интерфейса IEquatable<int>.Equals(int)

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

В четвертом случае мы снова должны определить какой именно метод Equals будет вызван. Вызывающий экземпляр имеет тип short, который опять же имеет три метода Equals.

Equals(object, object) // статический метод унаследованный от object
Equals(object)         // виртуальный метод унаследованный от object
Equals(short)          // реализация метода интерфейса IEquatable<short>.Equals(short)

Первый и третий методы нам не подходят, потому что для первого у нас слишком мало аргументов, а третий метод не будет выбран потому что нет неявного приведения типа int к short. Остается метод short.Equals(object), реализация которого равна следующему коду:

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

То есть, чтобы этот метод вернул true, упакованный элемент должен являться типом short, и после распаковки он должен равняться экземпляру который вызвал Equals. Но, так как, передаваемый аргумент является типом int, метод вернет false.

В пятом, шестом и седьмом, будет выбрана форма сравнения object == object, что эквивалентно вызову метода Object.ReferenceEquals. Очевидно, что две ссылки равны в пятом случае и неравны в шестом и седьмом. Значения которые содержатся в переменных типа object неважны, потому что сравнение по значению не используется совсем, сравниваются только ссылки.

В восьмом и девятом случае, будет использован статический метод Object.Equals, который реализован следующим образом:

public static bool Equals(object x, object y)
{
    if (ReferenceEquals(x, y)) return true;
    if (ReferenceEquals(x, null)) return false;
    if (ReferenceEquals(y, null)) return false;
    return x.Equals(y);
}

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

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

Так как аргумент является упакованной переменной типа int, будет произведено сравнение по значению и метод вернет true. В девятом случае упакованная переменная имеет тип short, следовательно проверка типа (z is int) завершится неудачей, и метод вернет false.

Итог:
Я показал девять различных способов сравнения двух переменных, несмотря на то, что во всех случаях, сравниваемые переменные равны единице, только в половине случаев сравнение возвращает true. Если вы думаете, что это сумасшествие и все запутанно, вы правы! Сравнение в C# очень коварно.

Автор: BloodUnit

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js