От переводчика:
Это вольный перевод блогозаписи Эрика Липперта (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# очень коварно.Only registered users can participate in poll. Log in, please.
Сколько результатов сравнений для вас оказались неожиданными?
22.37% Ничего нового, все логично85
48.68% 1-2185
21.32% 3-481
7.63% 5+29
380 users voted. 114 users abstained.