📜 ⬆️ ⬇️

C #. Inconsistent comparison


From the translator:
This is a free translation of Eric Lippert's blog post ( Eric Lippert ), in the past one of the developers of the C # language. The record is framed in the form of "question-answer", I will skip the question and go to the answer, you can read the original question, but there is nothing particularly interesting.

But, for a start, I will ask you to look at the following code, and without Google and compiling, try to find out what happens in 9 cases of comparison and check the answers (for the survey):
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 



The C # language was designed to work the way the developer expects: that is, a language where the obvious techniques and the correct techniques are the same. And for the most part it is. Unfortunately, the comparison is one of the parts of the language in which there are traps.
')
We write the following code to illustrate the different degrees of comparison.
Answers
 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!?! 


What the hell? Let's sort everything out in order.

In the first and second cases, we must first determine what the == operator means. In C #, there are more than a dozen built-in operators == for comparing different types.
 object == object string == string int == int uint == uint long == long ulong == ulong ... 

Since there are no int == short or short == int operators, the most appropriate operator must be selected. In our case, this operator is int == int . Thus, the short converted to an int and then the two variables are compared by value. Therefore, they are equal.

In the third case, we must first determine which of the overloaded Equals methods will be called. The calling instance is of type int , and it implements the three Equals methods.
 Equals(object, object) //     object Equals(object) //     object Equals(int) //    IEquatable<int>.Equals(int) 

The first one doesn’t suit us because we don’t have enough arguments to call it. Of the other two methods, the method that takes an int as a parameter is more suitable, it is always better to convert a short argument to an int than an object . Therefore, Equals(int) will be called, which compares two variables of type int using comparison by value, so this expression is true.

In the fourth case, we must again determine which particular Equals method will be called. The caller has a short type, which again has three Equals methods.
 Equals(object, object) //     object Equals(object) //     object Equals(short) //    IEquatable<short>.Equals(short) 

The first and third methods do not suit us, because for the first we have too few arguments, and the third method will not be chosen because there is no implicit type conversion of int to short . short.Equals(object) leaves the short.Equals(object) method, the implementation of which is equal to the following code:
 bool Equals(object z) { return z is short && (short)z == this; } 

That is, in order for this method to return true , the packaged element must be of the short type, and after unpacking it must be equal to the instance that caused Equals . But, since the argument passed is of type int , the method will return false .

In the fifth , sixth and seventh , the form of comparison object == object will be chosen, which is equivalent to calling the Object.ReferenceEquals method. Obviously, the two links are equal in the fifth case and unequal in the sixth and seventh . The values ​​contained in object type variables are unimportant, because comparison by value is not used at all, only references are compared.

In the eighth and ninth cases, the static Object.Equals method will be used, which is implemented as follows:
 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); } 

In the eighth case, we have two links that are not equal and not null , therefore, int.Equals(object) will be called, which, as you can assume when looking at the code of the short.Equals(object) method, is implemented as follows:
 bool Equals(object z) { return z is int && (int)z == this; } 

Since the argument is a packed variable of type int , a comparison will be made by value and the method will return true . In the ninth case, the packaged variable is of type short , hence the type check ( z is int ) will fail, and the method will return false .

Outcome :
I showed nine different ways of comparing two variables, despite the fact that in all cases the compared variables are equal to one, only in half of the cases the comparison returns true . If you think this is crazy and everything is confusing, you are right! Comparison in C # is very insidious.

Source: https://habr.com/ru/post/209412/


All Articles