-2

In Java the following code can return false:

IntegerPlus o1 = new IntegerPlus(1000);
IntegerPlus o2 = o1;

boolean b1 = o1 == o2;
boolean b2 = o1.Equals (o2);

Is this also a problem in C#? Or does C# perform the == in a way where it will always be true even if objects get moved? (I describe the Java issue in greater detail here.)

David Thielen
  • 28,723
  • 34
  • 119
  • 193
  • 7
    Why don't you try yourself and see? – Soner Gönül Mar 30 '14 at 18:09
  • 6
    @SonerGönül the problem is this got exposed in Java only after years of use and millions of executions. So not something I can duplicate and say no error answers the question. – David Thielen Mar 30 '14 at 18:10
  • [this article](http://whyjavasucks.com/Blog/6/Java_vs_CSharp/4092/Operator_Overloading_Is_Useless_By_Example) explains the differences between C# and java in a clear way. – Federico Berasategui Mar 30 '14 at 18:16
  • 4
    What you're claiming is that if you have two variables pointing to the very same instance, `==` can still return false in java? In that case you could simplify your example to `new Object()` to make it more clear that you're not talking about overloading `==`. – CodesInChaos Mar 30 '14 at 18:16
  • 2
    I noticed only after reading this question twice that it is **unrelated** to operator overloading for comparing two different instances. – O. R. Mapper Mar 30 '14 at 18:18
  • 1
    Does the java specification actually permit this insane behaviour? Or is it a bug in the JVM (or your test)? – CodesInChaos Mar 30 '14 at 18:23
  • @CodesInChaos - the test is definitely valid. If you read the full story on the link, changing it to use getReferenceId() fixed the issue. As to a bug in the JVM, we've seen this for years so I think it's allowed. I think... – David Thielen Mar 30 '14 at 18:27
  • @DavidThielen consider editing your question to make more clear what the problem is... Maybe inclining core issue from article and updating sample to highlight what exactly can return false and in what condition. – Alexei Levenkov Mar 30 '14 at 18:40
  • 2
    @DavidThielen Your article is misleading. You are describing a bug in the JVM as though it is actually allowed behavior. The Java Language Specification disallows the described semantics going back to *at least* Java 5. – Sam Harwell Apr 01 '14 at 20:56
  • -1 because the premise of this question is incorrect. Java requires that `b1` be `true` following the execution of the code above, and `b2` can be anything (based on the user-defined implementation of `Equals` which is not presented here). – Sam Harwell Apr 01 '14 at 21:00
  • @280Z28 The spec may say that b1 must be true, but we have seen several implementation where, under load (ie the garbage collector is moving stuff around), it is false. It's a bug that has existed for years. And as it does exist, we need to code around it. As to b2, if you go to the link, I say if there is no override of Equals(). – David Thielen Apr 01 '14 at 22:40
  • 2
    @DavidThielen You presented this as a case where people overlook a nuance of the Java language. That is not the case, and it's completely useless as a bug report (at minimum it needs vendor(s), version(s), and the arguments passed to the JVM). Your explanation of the "probable cause" is wildly inaccurate and should be removed so it doesn't mislead other developers. – Sam Harwell Apr 01 '14 at 22:45
  • 1
    @280Z28 I think my explanation is what is actually happening. I am open to alternative causes but that's the best I've come up with. As to the vendor/version - it's happening under the latest release from Oracle. Also every earlier version from them and from IBM. IBM verified the bug years ago, but did not fix it. – David Thielen Apr 02 '14 at 02:04
  • 3
    Sadly this completely unfounded conjecture here and in the linked blog article is still confusing people, as seen in for example [this post](http://stackoverflow.com/questions/34739112/can-the-jvm-gc-move-objects-in-the-middle-of-a-reference-comparison-causing-a-c). GCs are complicated - particularly the highly optimized Java ones - so it's certainly possible that there's a bug in it, but **no** optimization is allowed to change observable program behavior and [JLS §15.21.3](https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3) is very clear on the expected behavior of ==. – Voo Jan 12 '16 at 20:10
  • @Voo IBM verified for us that this error existed in their JVM. No idea if it still exists, but it was a verified bug in their JVM. – David Thielen Jan 13 '16 at 23:46
  • 2
    @David There's a large difference between "a bug in one particular JVM" and "this is not safe according to the language specification". Otherwise I would propose for your next article/SO post the combination "Do not use recursion in C#!" and "Is recursion safe in Java?" (that one RyuJit bug wrt tail call optimisation seems fitting considering we're on SO, but there are many others). – Voo Jan 14 '16 at 07:34
  • @Voo At the time, and my memory is fuzzy as it was a long time ago, the answer I got from IBM and some others was that the language did allow this. Unfortunately I don't remember the specifics. – David Thielen Jan 14 '16 at 23:20
  • 1
    IBM supposedly said that the == operator randomly failing is allowed behavior? I can't imagine that. – Stefan Reich Dec 08 '17 at 02:08

2 Answers2

2

No.

In C#/.Net hash code is not involved into default implementation of ==, != or Equals. If object is reference type and it is moved by GC there is nothing outside of this object that will impact default comparisons.

Edit: unrelated details about comparison below.

== and Equals are not related to each other (except by convention).

In C# you have operator == that class/struct can overload. Default behavior is to compare references for reference types, value compare for system value types and for custom 'struct' there is no == operator auto-generated. Note that the other comparison Equals is not involved into default definition of operator ==.

For predefined value types, the equality operator (==) returns true if the values of its operands are equal, false otherwise. For reference types other than string, == returns true if its two operands refer to the same object.

You can also provide your custom Object.Equals for class. Good practice for classes the redefine == or Equals is to redefine all comparison related methods to work consistently (==, !=, Equals(object other), and GetHashCode; possibly Equals(myType other)).

The only usage of GetHashCode in .Net is hash-based collections like Dictionary, HashSet. Check GetHashCode Guidelines in C#

Obvious sample of custom comparison is System.String (string) - it is reference type but behaves as regular value type in relation to comparison.

So back to the original sample:

  • if IntegerPlus is struct without custom "=="/Equals: there is no == automatically created (new IntegerPlus(42) == new IntegerPluss(42) - syntax error). You get value comparison for all fields for Equals (automatically provided).
  • if IntegerPlus is class that does not provide custom operator == or Equals you get reference comparison and new IntegerPlus(42) != new IntegerPluss(42) and same for Equals
  • if IntegerPlus provide only one of ==, !=, Equals than behavior will be as defined by custom implementation (likely unexplainable by external observer)
  • if IntegerPlus is either value or reference type and provides consistent set of all 4 comparison related methods you can get value comparison behavior
Community
  • 1
  • 1
Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • 2
    I thought of this at first, too, but I think this doesn't answer the question. Note that this is not about using `==` between two *different* instances of a type that may have an overloaded `==` operator in C#, but about `==` returning `false` even though both sides refer to the *same instance*. – O. R. Mapper Mar 30 '14 at 18:17
  • @O.R.Mapper - yes, thank you. Assume that == is purposely not overloaded when we want to test for the same object. – David Thielen Mar 30 '14 at 18:26
  • @O.R.Mapper - I did not get it from the post... indeed this is completely unrelated post - sorry. – Alexei Levenkov Mar 30 '14 at 18:30
  • @DavidThielen - see if my edit makes more sense in relation to your question. – Alexei Levenkov Mar 30 '14 at 18:36
  • @AlexeiLevenkov - thank you, yes the more detailed one helps. BTW, I don't think Java uses the hashCode either as that would give false true results. I think is uses the System.identityHashCode which is a slightly different thing. Out of curiosity, what does C# compare when calling == on two objects where they do not define the == operator? – David Thielen Mar 30 '14 at 23:01
  • @DavidThielen Check out http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx - I think it will give you the answer. For reference types "By default, the operator == tests for reference equality by determining if two references indicate the same object", I'm not sure how "==" is handled internally for value types. – Alexei Levenkov Mar 30 '14 at 23:20
  • @AlexeiLevenkov - they don't say how though. I'm assuming it's a means that is 100% safe as othewise people would have found a problem similiar to what we found in Java. But I wish they would tell us how they do it. Oh well... – David Thielen Mar 31 '14 at 16:08
  • @DavidThielen - I'm not sure there are many options - reference is just a pointer to heap. How many ways to compare two 32bit (or 64bit) values for "==" do you know? You can read on memory layout in [How CLR Creates Runtime objects](http://msdn.microsoft.com/en-us/magazine/cc163791.aspx) article - maybe it will help you with this. – Alexei Levenkov Mar 31 '14 at 16:48
  • 1
    Types cannot *override* the `==` operator; they may define overloads. Unlike overrides of `Equals`, which are processed based upon the actual type of an object instance, `==` overloads are based upon the *compile-time* type of the thing being compared. Primitive value-types and *some* non-primitive ones have pre-defined overloads, but value types which do not *explicitly* define an overload for `==` cannot be used with that operator [value types do, by default, *override* the `Equals(Object)` method]. – supercat Apr 01 '14 at 19:01
1

In Java, the == operator used to be safe with all operand combinations that would compile, and provided neither operand was float nor double, it would implement an equivalence relation. With the advent of autoboxing that is no longer quite true, since the unfortunately-legal comparisons between primitives and their wrapped equivalent is not safe and extends the == operator in a manner inconsistent with the equivalence relations defined by the types individually.

While the equivalence relation which Java would test when both operands were reference types (i.e. reference equivalence) was not always the one programmers were interested in, the behavior of the == was consistent with reference types. If variables x, y, and z are any combination of reference types and the expressions x==y, y==z, and x==z all compile, then in every case where two are true, the third will be as well.

In C#, the == token is used to represent two different operators: the overloadable equality test, and a default reference-equality test. If one of the operands to == defines an overload which is applicable to the particular combination of operands supplied, the token will be interpreted as an overloadable equality-test operator. Otherwise, if the operands are compatible reference types, it will be interpreted as the default reference-equality test operator. Because the same token is used for both operators and they have different semantics, it cannot in general implement a reliable equivalence relation among reference types unless none of the types in question overload it to represent anything other than reference equality.

Given the definitions String s1=123.ToString(); String s2=123.ToString(); Object o = s1;, the comparison s1==s2 will return True (using the (string,string) overload of ==, which sees that they contain the same characters), and o==s1 will return true (using the default reference-equivalence operator to see that they re the same object), but o==s2 will return false (using the default reference-equivalence operator to see that they are distinct objects).

If both operands to == are of type Object, then it will behave as a reference equality test; if they are of other types, though, then the operator may sometimes behave as a reference equality test and sometimes as something else.

supercat
  • 77,689
  • 9
  • 166
  • 211