Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equality operator overloading in struct and classes

Tags:

c#

.net

If I do the overloading of operator == for a class, I must perform some checks before comparing the fields:

  • if both arguments are null, or both arguments are the same instance, then returns true

    Example: if (System.Object.ReferenceEquals(arg1, arg2)) return true;

  • if one is null, but not both, then returns false

    Example: if (((object)arg1 == null) || ((object)arg2 == null)) return false;

Indeed, if I have a struct and I want to do the overloading of operator ==, these checks are not necessary, rather they are useless, for the following reasons: a struct is a value type, so it can not be null, for example DateTime date = null; is not valid, because DateTime (that is a struct) is not a reference type, so you can not compare two DateTime, one of which is set to null.

I created a simple struct Point2D with operator ==, then I compare an instance of Point2D with null:

Point2D point = new Point2D(0,0);
Console.WriteLine((point == null));
  1. Obviously the operator == it is not called, but the comparison returns False. Which method is called?

  2. The documentation states that overloading this operator in not-immutable types is not recommended. Why?

like image 799
enzom83 Avatar asked Feb 08 '12 01:02

enzom83


2 Answers

Chris Shain's answer is correct but does not explain why this is legal.

When you override the equality operator, and both the operands are non-nullable value types, and the the return type is bool, then for free we give you a lifted operator. That is, if you have

public static bool operator ==(S s1, S s2) { ... }

then for no additional cost you get

public static bool operator ==(S? s1, S? s2) { ... }

It is that operator that is being called. Of course the compiler knows that the result will always be false because one of the operands is null and the other never is.

There used to be a warning to the effect that your code always returns false but we accidentally disabled it a couple versions back and never actually turned it back on. I'm working on this very code tomorrow in the Roslyn compiler so I'll see what I can do to get it back in shape.

like image 72
Eric Lippert Avatar answered Oct 08 '22 07:10

Eric Lippert


Because it seems that the compiler optimizes this away. I tried this code:

System.Drawing.Point point = new System.Drawing.Point(0,0);
Console.WriteLine((point == null));

And it generated the following IL:

IL_0000:  ldloca.s    00 
IL_0002:  ldc.i4.0    
IL_0003:  ldc.i4.0    
IL_0004:  call        System.Drawing.Point..ctor
IL_0009:  ldc.i4.0    
IL_000A:  call        System.Console.WriteLine

This ultimately boils down to "Create a Point, and then write false to the command line"

This also explains why it doesn't call your operator. A struct can never be null, and in circumstances where the compiler can guarantee that you are always going to get false as a result, it doesn't bother issuing code to call an operator at all.

The same thing happens with this code, even though String is a class and overload the == operator:

System.Drawing.Point point = new System.Drawing.Point(0,0);
Console.WriteLine("foo" == null);

As for immutability... The == operator in C# is generally interpreted to mean "reference equality", e.g. these two variables point to the same instance of the class. If you are overloading it, then you generally mean to say that two instances of the class, while not the same instance, should behave as if they were the same instance when their data is the same. The classic example is Strings. "A" == GiveMeAnA() even though the actual String reference returned by GiveMeAnA may not be the same as the one represented by the literal "A".

If you overloaded the == operator on classes which were not immutable, then mutation of a class after == had been evaluated could cause numerous subtle bugs.

like image 43
Chris Shain Avatar answered Oct 08 '22 07:10

Chris Shain