Why is this a stackoverflowException statement definition?

Please see my comment in the code below. How to check if a parameter is null ? It looks like null sent to Foo , which essentially makes a recursive call with the == operator. Why is this happening?

 public class Foo { public static bool operator ==(Foo f1, Foo f2) { if (f1 == null) //This throw a StackOverflowException return f2 == null; if (f2 == null) return f1 == null; else return f1.Equals((object)f2); } public static bool operator !=(Foo f1, Foo f2) { return !(f1 == f2); } public override bool Equals(object obj) { Foo f = obj as Foo; if (f == (Foo)null) return false; return false; } public override int GetHashCode() { return 0; } } 
+6
source share
1 answer

Why is this happening?

Because the rules of the language speak.

You have provided an operator with this signature:

 public static bool operator ==(Foo f1, Foo f2) 

and then - wherever this happens in the code - you get this expression:

 f1 == null 

where f1 has the compile time type Foo . Now null implicitly converted to Foo , so why not use your operator? And if you have the first line of your statement unconditionally calling itself, you should expect a stack overflow ...

To prevent this from happening, you will need one of two changes in the language:

  • The language should have a special case, which meant == when it was used in the declaration for == . Hk.
  • The language would have to decide that any == expression with one null operand always meant a comparison of references.

Nothing special, IMO. Avoiding this by simply avoiding redundancy and adding optimization:

 public static bool operator ==(Foo f1, Foo f2) { if (object.ReferenceEquals(f1, f2)) { return true; } if (object.ReferenceEquals(f1, null) || object.ReferenceEquals(f2, null)) { return false; } return f1.Equals(f2); } 

However, then you need to fix your Equals method, because then it will return to your == , which will lead to another stack overflow. You never really talked about how you want to define equality ...

I usually had something like this:

 // Where possible, define equality on sealed types. // It gets messier otherwise... public sealed class Foo : IEquatable<Foo> { public static bool operator ==(Foo f1, Foo f2) { if (object.ReferenceEquals(f1, f2)) { return true; } if (object.ReferenceEquals(f1, null) || object.ReferenceEquals(f2, null)) { return false; } // Perform actual equality check here } public override bool Equals(object other) { return this == (other as Foo); } public bool Equals(Foo other) { return this == other; } public static bool operator !=(Foo f1, Foo f2) { return !(f1 == f2); } public override int GetHashCode() { // Compute hash code here } } 

Please note that this only allows you to worry about validation checks in one place. To avoid over comparing f1 to null when it was called using the Equals instance method to start, you can delegate == to Equals after checking for the invalidity of f1 , but I'd probably stick with this.

+25
source

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


All Articles