
От переводчика:
Это вольный перевод блогозаписи Эрика Липперта (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# очень коварно.
ссылка на оригинал статьи http://habrahabr.ru/post/209412/
Добавить комментарий