180

Consider this code:

int age = 25;
short newAge = 25;
Console.WriteLine(age == newAge);  //true
Console.WriteLine(newAge.Equals(age)); //false
Console.ReadLine();

Both int and short are primitive types, but a comparison with == returns true and a comparison with Equals returns false.

Why?

Martijn Pieters
  • 889,049
  • 245
  • 3,507
  • 2,997
Mohammad Zargarani
  • 1,402
  • 2
  • 12
  • 19
  • 9
    @OrangeDog Please Think about question and then vote to close –  Jan 22 '14 at 10:00
  • 4
    This is missing the obvious reverse attempt: `Console.WriteLine(age.Equals(newAge));` – ANeves thinks SE is evil Jan 22 '14 at 10:47
  • Both questions could be answered by the same explanation of how the two syntaxes differ in semantics. If you feel otherwise, then the titles of the questions should be changed. – OrangeDog Jan 22 '14 at 10:48
  • 3
    The duplicate does not explain this behavior; it's just about what `Equals()` is in general. – SLaks Jan 22 '14 at 12:30
  • 2
    @OrangeDog this question is not duplicate –  Jan 22 '14 at 13:21
  • 37
    I answered this exact question on the Coverity blog a few days ago. http://blog.coverity.com/2014/01/13/inconsistent-equality/ – Eric Lippert Jan 22 '14 at 14:41
  • 1
    What happens with `age.Equals(newAge);`? – Proxy Jan 22 '14 at 15:23
  • @OrangeDog There is no concept of "primitive types" in C#, and even if it existed it'd be irrelevant. This is about overloading `==` and overriding `Equals` to obtain value instead of reference semantics. – CodesInChaos Jan 22 '14 at 16:17
  • I'm not sure how related it is to this question, but `double.NaN` is not `==`-equal to itself, but it *does* `Equals` itself. – harold Jan 22 '14 at 16:30
  • ReSharper says: Suspicious comparison: there is no type in the solution which is inherited from both 'short' and 'int' – Raz Megrelidze Jan 22 '14 at 18:34
  • 5
    @CodesInChaos: The specification actually uses the term "primitive types" twice without ever defining it; the implication is that primitive types are built-in value types, but this is never made clear. I've recommended to Mads that the term simply be stricken from the specification as it seems to create more confusion than it removes. – Eric Lippert Jan 22 '14 at 21:24
  • Please see: http://stackoverflow.com/a/2111905 – jason Jan 23 '14 at 03:11
  • 1
    @Jason Do you know about C#? –  Jan 23 '14 at 03:25
  • 1
    @Jason What is related between boxing and unboxing and this question? –  Jan 23 '14 at 03:26
  • C# unlike Java, doesn't actually have Primitives. Numbers etc are Reference type (to use java terms). Most things that in Java would be called Primitives are in C# just types that implement `IConvertible, IComparable, IEquatable` I think the important thing here is the `IEquatable` interface (and the rules and conventions around it.) – Lyndon White Jan 23 '14 at 12:19
  • 1
    @Oxinabox: Completely wrong. Numbers in C# are value types, and can be boxed to `object`, which is causing the problem here. This has nothing to do with `IEquatable`. – SLaks Jan 23 '14 at 12:54
  • @SLaks: C# v1033 Spec, Page 1: `C# has a unified type system. All C# types, including primitive types such as int and double, inherit from a single root object type. Thus, all types share a set of common operations, and values of any type can be stored, transported, and *operated upon in a consistent manner.* ` The word Primitive is mentioned only one other time in the spec, page 361 (paraphased for context) `"Primitive" [sic] types are implemented as structs` For all intents and purposes the word primitive has no meaning in the C# language. The issue might actually relate to `structs` though – Lyndon White Jan 23 '14 at 13:02
  • @Oxinabox: Yes (see Eric Lippert's comment), but value types are not reference types (unless boxed). Although this particular issue would still happen with reference types too. – SLaks Jan 23 '14 at 13:04
  • @SLacks: Sorry I miss interpretted "Completely wrong", You are correct, numbers etc are Value types, not refernce types (my bad). Because they are structs. Perhaps the question should be editted to say `value types` instead of "primitives". My issue is that the primitve word shouldn't be used here. – Lyndon White Jan 23 '14 at 13:05
  • 2
    This is not actually a duplicate of either of those questions. For example, this behavior is completely independent of boxing. – SLaks Jan 23 '14 at 15:57
  • 1
    Any good reason why this is tagged javascript? – bfavaretto Jan 28 '14 at 19:08

9 Answers9

266

Short answer:

Equality is complicated.

Detailed answer:

Primitives types override the base object.Equals(object) and return true if the boxed object is of the same type and value. (Note that it will also work for nullable types; non-null nullable types always box to an instance of the underlying type.)

Since newAge is a short, its Equals(object) method only returns true if you pass a boxed short with the same value. You're passing a boxed int, so it returns false.

By contrast, the == operator is defined as taking two ints (or shorts or longs).
When you call it with an int and a short, the compiler will implicitly convert the short to int and compare the resulting ints by value.

Other ways to make it work

Primitive types also have their own Equals() method that accepts the same type.
If you write age.Equals(newAge), the compiler will select int.Equals(int) as the best overload and implicitly convert short to int. It will then return true, since this method simply compares the ints directly.

short also has a short.Equals(short) method, but int cannot be implicitly converted to short, so you aren't calling it.

You could force it to call this method with a cast:

Console.WriteLine(newAge.Equals((short)age)); // true

This will call short.Equals(short) directly, without boxing. If age is larger than 32767, it will throw an overflow exception.

You could also call the short.Equals(object) overload, but explicitly pass a boxed object so that it gets the same type:

Console.WriteLine(newAge.Equals((object)(short)age)); // true

Like the previous alternative, this will throw an overflow if it doesn't fit in a short. Unlike the previous solution, it will box the short into an object, wasting time and memory.

Source Code:

Here are both Equals() methods from the actual source code:

    public override bool Equals(Object obj) {
        if (!(obj is Int16)) {
            return false;
        }
        return m_value == ((Int16)obj).m_value;
    }

    public bool Equals(Int16 obj)
    {
        return m_value == obj;
    }

Further Reading:

See Eric Lippert.

Community
  • 1
  • 1
SLaks
  • 800,742
  • 167
  • 1,811
  • 1,896
  • 3
    @SLaks, if we call `long == int`, `int` implicitly converted to `long` right? – Selman Genç Jan 22 '14 at 04:07
  • 1
    And yes, I wrote all that up without actually trying it. – SLaks Jan 22 '14 at 04:16
  • @Selman22 for better readability you should still use a cast though. – Mark Jan 22 '14 at 11:01
  • 1
    Remember that, in the code of the question, if one changes `int age = 25;` to `const int age = 25;`, then the outcome will change. That is because an implicit conversion from `int` to `short` does exist in that case. See [Implicit *constant* expression conversions](http://msdn.microsoft.com/en-us/library/aa691286.aspx). – Jeppe Stig Nielsen Jan 22 '14 at 15:33
  • Great answer. One slight nit pick The `Equals(object)` override will work with both the same type or nullable version of the same type – JaredPar Jan 22 '14 at 16:05
  • 1
    @JaredPar: Technically, that's irrelevant; nullables always box to the underlying type, so it doesn't even see nullablity. – SLaks Jan 22 '14 at 16:08
  • 2
    @SLaks yes but the wording of your answer "the passed value" can be interpreted both ways (as the value passed by the developer, or the value which is actually passed by the CLR after unboxing). I'm guessing the casual user who doesn't already know the answers here will read it as the former – JaredPar Jan 22 '14 at 16:10
  • @JaredPar: True; clarified. – SLaks Jan 22 '14 at 16:25
  • Short answer that I always remember: *`.Equals()` compares Equality while `==` compares Identity – Rachel Jan 22 '14 at 19:56
  • 2
    @Rachel: Except that that's not true; the _default_ `==` operator compares reference types by reference. For value types, and for types that overload `==`, it doesn't. – SLaks Jan 22 '14 at 20:04
  • @SLaks Yes, I know there are some cases where this doesn't exactly apply, however in general I find that short phrase a good way to remember which comparison method I should use, and in which cases. `==` compares Identity (ie. object reference in memory) while `.Equals()` measures Equality (if one object can be considered equal to another, which can be user-defined). Sure you could overload the `==` operator, and sure the compiler will treat value types a bit differently (afterall, those types are known in advance), but its a good short way to remember the differences in general. – Rachel Jan 22 '14 at 20:22
  • 1
    Re : *"if age is larger than 32767, it will throw an overflow exception."* This is only true in a checked context. In an unchecked context it will truncate. – Eric Lippert Jan 22 '14 at 21:27
  • Does writing if ((age - newAge) == 0) help in any way? – frenchie Jan 23 '14 at 09:57
  • @frenchie: That will implicitly convert the `short` to `int` for the `-` operator, just like `==` does. – SLaks Jan 23 '14 at 12:55
  • Also, the _pass in a boxed type_ doesn't seem correct. Passing in a boxed type would be calling `a.Equals((object)b)` wouldn't it? _Pass in a short_ would be more precise. Yes it's boxed eventually, but only in the code that is run in the `Equals` method. – Thraka Jan 23 '14 at 15:26
  • 1
    @Thraka: Wrong. `int` is not convertible to `short`. Instead, it calls `Equals(object)`, implicitly boxing it at the callsite. Read my answer more carefully. – SLaks Jan 23 '14 at 15:29
55

Because there is no overload for short.Equals that accepts an int. Therefore, this is called:

public override bool Equals(object obj)
{
    return obj is short && this == (short)obj;
}

obj is not a short.. therefore, it is false.

Majid
  • 12,271
  • 14
  • 71
  • 107
Simon Whitehead
  • 57,414
  • 7
  • 93
  • 127
12

For value types, .Equals requires the two objects to be of the same type and have the same value, while == just tests if the two values are the same.

Object.Equals
http://msdn.microsoft.com/en-us/library/bsc2ak47(v=vs.110).aspx

Majid
  • 12,271
  • 14
  • 71
  • 107
Wagner DosAnjos
  • 6,076
  • 1
  • 11
  • 25
12

When you pass int to short's Equals you pass object:

enter image description here So this pseudocode runs:

return obj is short && this == (short)obj;
Majid
  • 12,271
  • 14
  • 71
  • 107
10

== is used for checking a equal condition, it can be considered as an operator(boolean operator), just to compare 2 things and here the data type doesn't matter as there would be a type casting done and Equals is also used for checking equals condition, but in this case the data types should be same. N Equals is a method not an operator.

Below is a small example taken from the one you provided and this will clarify difference in brief,.

int x=1;
short y=1;
x==y;//true
y.Equals(x);//false

in the above example, X and Y have same values i.e. 1, and when we use ==, it will return true, as in case of ==, the short type is converted to int by the compiler and the result is given.

and when we use Equals, the comparing is done, but the type casting is not done by compiler, so false is returned.

Guys, please let me know if I'm wrong.

Majid
  • 12,271
  • 14
  • 71
  • 107
user2423959
  • 806
  • 1
  • 12
  • 26
6

Equals() is a method of System.Object Class
Syntax : Public virtual bool Equals()
Recommendation if we want to compare state of two objects then we should use Equals() method

as stated above answers == operators compare the values are same.

Please don't get confused with ReferenceEqual

Reference Equals()
Syntax : public static bool ReferenceEquals()
It determine whether the specified objects instance are of the same instance

Sugat Mankar
  • 448
  • 3
  • 16
6

In many contexts where a method or operator argument is not of the required type, the C# compiler will attempt to perform an implicit type conversion. If the compiler can make all arguments satisfy their operators and methods by adding implicit conversions, it will do so without complaint, even though in some cases (especially with equality tests!) the results may be surprising.

Further, each value type such as int or short actually describes both a kind of value and a kind of object(*). Implicit conversions exist to convert values to other kinds of values, and to convert any kind of value to its corresponding kind of object, but the different kinds of objects are not implicitly convertible to each other.

If one uses the == operator to compare a short and an int, the short will be implicitly converted to an int. If its numerical value was equal to that of the int, the int to which it was converted will equal the int to which it is compared. If one attempts to use the Equals method on the short to compare it with an int, however, the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to int. When the short is asked whether it matches the passed-in object, it will observe that the object in question is an int rather than a short and thus conclude that it cannot possibly be equal.

In general, although the compiler won't complain about it, one should avoid comparing things which are not of the same type; if one is interested in whether conversion of things to a common form would give the same result, one should perform such conversion explicitly. Consider, for example,

int i = 16777217;
float f = 16777216.0f;

Console.WriteLine("{0}", i==f);

There are three ways in which one might want to compare an int to a float. One might want to know:

  1. Does the closest possible float value to the int match the float?
  2. Does the whole-number part of the float match the int?
  3. Do the int and float represent the same numerical value.

If one tries to compare an int and float directly, the compiled code will answer the first question; whether that's what the programmer intended, however, will be far from obvious. Changing the comparison to (float)i == f would make it clear that the first meaning was intended, or (double)i == (double)f would cause the code to answer the third question (and make it clear that's what was intended).

(*) Even if the C# spec regards a value of type e.g. System.Int32 as being an object of type System.Int32, such a view is contradicted by the requirement that a code run on a platform whose spec regards values and objects as inhabiting different universes. Further, if T is a reference type, and x is a T, then a reference of type T should be able to refer to x. Thus, if a variable v of type Int32 holds an Object, a reference of type Object should be able to hold a reference to v or its contents. In fact, a reference of type Object would be able to point to an object holding data copied from v, but not to v itself nor to its contents. That would suggest that neither v nor its contents is really an Object.

supercat
  • 69,493
  • 7
  • 143
  • 184
  • 1
    `the only implicit conversion which would satisfy an overload of the Equals method would be the conversion to the object type corresponding to int` Wrong. Unlike Java, C# does not have separate primitive and boxed types. It's being boxed to `object` because that's the only other overload of `Equals()`. – SLaks Jan 22 '14 at 18:23
  • The first and third question are identical; the exact value was already lost upon conversion to `float`. Casting a `float` to a `double` will not magically create new precision. – SLaks Jan 22 '14 at 18:28
  • @SLaks: According to the ECMA spec, which describes the virtual machine upon which C# runs, each value type definition creates two distinct types. The C# spec may say that the contents of a storage location of type `List.Enumerator` and a heap object of type `List.Enumerator` are the same, but the ECMA/CLI spec says they're different, and even when used in C# they behave differently. – supercat Jan 22 '14 at 18:32
  • @SLaks: If `i` and `f` were each converted to `double` before the comparison, they would yield 16777217.0 and 16777216.0, which compare as unequal. Converting `i` `float` would yield 16777216.0f, comparing equal to `f`. – supercat Jan 22 '14 at 18:34
  • @SLaks: For a simple example of the difference between storage-location types and boxed object types, consider the method `bool SelfSame(T p) { return Object.ReferenceEquals((Object)p,(Object)p);}`. The boxed object type corresponding to a value type can satisfy the parameter type of `ReferenceEquals` via an *identity preserving* upcast; the storage location type, however, requires a *non-identity-preserving* conversion. If casting a `T` to `U` yields a reference to something other than the original `T`, that would suggest to me that a `T` isn't really a `U`. – supercat Jan 22 '14 at 18:47
  • @supercat Is it actually possible to use the "boxed int" type as `T`? AFAIR C++/CLI which has the syntax to represent "boxed int" with `int^` uses `ValueType` or `Object` for those with some custom tagging which would be a bit weird if you can actually use those. – CodesInChaos Jan 22 '14 at 21:46
  • @CodesInChaos: When a type like `System.Int32` is used to describe a storage location, it identifies the storage location type. When it is used to describe a heap object, it identifies the heap object type. The CLR provides no means of defining a reference-type storage location which is constrained to hold references only to heap objects of type `Int32`. – supercat Jan 22 '14 at 22:09
4

What you need to realize is that doing == will always end up calling a method. The question is whether calling == and Equals ends up calling/doing the same things.

With reference types, == will always 1st check whether the references are the same (Object.ReferenceEquals). Equals on the other hand can be overridden and may check whether some values are equal.

EDIT: to answer svick and add on SLaks comment, here is some IL code

int i1 = 0x22; // ldc.i4.s ie pushes an int32 on the stack
int i2 = 0x33; // ldc.i4.s 
short s1 = 0x11; // ldc.i4.s (same as for int32)
short s2 = 0x22; // ldc.i4.s 

s1 == i1 // ceq
i1 == s1 // ceq
i1 == i2 // ceq
s1 == s2 // ceq
// no difference between int and short for those 4 cases,
// anyway the shorts are pushed as integers.

i1.Equals(i2) // calls System.Int32.Equals
s1.Equals(s2) // calls System.Int16.Equals
i1.Equals(s1) // calls System.Int32.Equals: s1 is considered as an integer
// - again it was pushed as such on the stack)
s1.Equals(i1) // boxes the int32 then calls System.Int16.Equals
// - int16 has 2 Equals methods: one for in16 and one for Object.
// Casting an int32 into an int16 is not safe, so the Object overload
// must be used instead.
user276648
  • 5,199
  • 5
  • 51
  • 79
  • So what method does comparing two `int`s with == call? Hint: there is no `operator ==` method for `Int32`, but [there is one for `String`](http://msdn.microsoft.com/en-us/library/system.string.op_equality). – svick Jan 22 '14 at 14:50
  • 2
    This does not answer the question at all. – SLaks Jan 22 '14 at 15:56
  • @SLaks: it indeed does not answer the specific question about int and short comparison, you already answered it. I still feel it's interesting to explain that `==` doesn't just do magic, it eventually simply calls a method (most programmers probably never implemented/overridden any operator). Maybe I could have added a comment to your question instead of adding my own answer. Feel free to update yours if you feel what I said is relevant. – user276648 Jan 23 '14 at 00:21
  • Note that `==` on primitive types is not an overloaded operator, but an intrinsic language feature which compiles to the `ceq` IL instruction. – SLaks Jan 23 '14 at 00:38
3

== In Primitive

Console.WriteLine(age == newAge);          // true

In primitive comparison == operator behave quite obvious, In C# there are many == operator overload available.

  • string == string
  • int == int
  • uint == uint
  • long == long
  • many more

So in this case there is no implicit conversion from int to short but short to int is possible. So newAge is converted into int and comparison occurs which returns true as both holds same value. So it is equivalent to:

Console.WriteLine(age == (int)newAge);          // true

.Equals() in Primitive

Console.WriteLine(newAge.Equals(age));         //false

Here we need to see what Equals() method is, we calling Equals with a short type variable. So there are three possibilities:

  • Equals(object, object) // static method from object
  • Equals(object) // virtual method from object
  • Equals(short) // Implements IEquatable.Equals(short)

First type is not case here as number of arguments are different we calling with only one argument of type int. Third is also eliminated as mentioned above implicit conversion of int to short is not possible. So here Second type of Equals(object) is called. The short.Equals(object) is:

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

So here condition got tested z is short which is false as z is an int so it returns false.

Here is detailed article from Eric Lippert

Zaheer Ahmed
  • 26,435
  • 11
  • 70
  • 105