Operator overloading

mskeel

Well-known member
Joined
Oct 30, 2003
Messages
913
Location
Virginia, USA
This is a continuation of a thread that accidentally got carried away. Sorry about that.

Nerseus said:
Let me sum it up: if you like operator overloading, use it. If you dont, dont. If you overload Equals on a reference type (class), I dont care - just dont come work for me
I agree but I am confused by your ending remark. You seem to be implying that overloading the equals operator on a class is bad. Maybe I am confused by my C++ background but, if you dereference the pointers and check the values for equality whats the big deal? Is there something Im missing here? Obviously if you do a shallow check then you can get very wrong results, but a deep check should work just fine. I mean, overlaoding == in a class is the whole point so why would it be bad?
 
In my opinion, an == check on a reference type should always mean the same thing - am I pointing at the exact same object. If I want to see if two separate instances of a Customer class represent the same customer (say, defined by a CustomerNumber field) then Id compare on CustomerNumber or add a more suitable method.

For Value types, if Im going to compare them, I would definitely override == and Equals for performance reasons.

-ner
 
+, -, ++, --, <, >, <<, >>, * and / are not defined either for structs or classes. There is no ambiguity in operator overloading here. == and != are not defined for structs. There is no ambiguity here. For classes (and Nerseus shares my sentiments here), == and != are already defined. When you overload the == operator for a class, you arent adding behavior, you are modifying it, hence you introduce ambiguity.

For example, a programmer writes a custom ListViewItem class and overloads the == and != operators. If he unwittingly uses the == operator to check for equal reference, he encounters logic errors. On the other hand, if he pulls them directly from a ListViewItemCollection and uses the == operator to see if two are equivalent, he will inadvertently be checking for equal references, and again encounter a logic error.

+ always adds things. * always multiplies. == compares references on reference types. If you overload an operator and define it to do something other than what is expected you are bound to confuse people and cause problems. (We could make an exception for << and >>, since they are probably better know as stream operators than bitshift operators in C++.) From where I stand, overloading == to compare a few fields of two objects is akin to doing something like overloading + to change the Name property of your control in that you are not simply implementing the operator for a class, you are changing what that operator does.

mskeel said:
Maybe I am confused by my C++ background but, if you dereference the pointers and check the values for equality whats the big deal?
The C++ analogy isnt really applicable. Casting to an Object is not the same a dereferencing, and neither is using the Object.Equals() function. You dont get to see the pointers in .Net. In C++ the reference and the object it refers to are two entirely separate entities and can be handled separately, so there is no conflict when it comes to operator overloading. == is not predefined for classes, and you can not redefine it for pointers. In C# or VB, the reference and the object are unified to an extent (notice that we dont have the -> operator in C#), and their behavior is somewhere between that of a C++ pointer and a C++ class. We dont have the luxury of treating the object and the pointer as separate entities. There is no such thing as dereferencing a .Net reference.

So when you overload the C# == operator, in C++ terms it is more like overloading the == operator for a pointer to your class than overloading the operator on the class itself. Think about comparing two pointers to ExampleClass objects and experiencing bizarre behavior because the creator of the class found a way to overload the == operator for the type *ExampleClass instead of ExampleClass.
 
nerseus said:
For Value types, if Im going to compare them, I would definitely override == and Equals for performance reasons.
The performance argument isnt going to wash for me. Overloading operators inst about performance, its about readability. When I see an equals operator between two variables, I assume that you are actually checking for equality -- not whether two variables point to the same place in memory. You should use Object.ReferenceEquals() (as marble earlier pointed out) to do that. The fact that Object.Equals is the same as ReferenceEquals for Objects is merely a coincidence in the case of Objects and should not dictate behaviour for all things that inherit from type Object.

Look at the String class. The string class inherits from Object and overloads the equals operator. That equals operator, by definition, "Determines whether two String objects have the same value." Not whether two string have the same reference which is the result you would get by casting string to an object and then checking equality or using the Object.ReferenceEquals method. Choose just about any Class where an equals operator makes sense and youll find that it has been overloaded. It makes no sense to not do the same in classes you write.

That fact that you couldnt overload operators in VB.Net 1.1 doesnt mean that you shouldnt now that you can in VB.Net 2.0. Not being able to overload operators in VB.Net 1.1 was a deficiency, not a feature.

Im not trying to start any fights here, so please dont take this as being hostile. It just seems like you guys think that overloading equals is bad and since you seem like smart people I want to understand why you think that way becuase I dont see it.
 
I cant explain my point of view any more clearly. Ambiguity and potential for logic errors. Heres another: breaking changes. You upgrade your component from .Net 1.1 to 2.0. You think that it would be nifty to use == to compare for equivalency, overload ==, and all of a sudden nothing works. Why? All your old code used == to check references. == has always meant the same thing for everything except elemental (or instrinsic if you prefer) types: reference comparison.

Operator overloading, with the exception of ==, only adds functionality. Overloading == does not simply add function, it modifies it. The objection to overloading == is all the implications of modifying something as elementary as the definition of equality. In .Net, equality is defined (until redefined) as binary equality for simple types, reference equality for reference type, and not defined for value types, so dont break out your dictionary.

I understand your point of view, I just disagree. I dont expect you to agree with me, but I have put it half a dozen ways and if you still dont at least understand then I dont know what to tell you.

Also...
mskeel said:
The string class inherits from Object and overloads the equals operator.
The fact that the equals operator behaves irregularly on Strings isnt a matter of operator overloading. No where in the .Net Framework is the == operator redefined for Strings. VB and C# compilers translate a == between two strings into a String.Equals() call. The compilers give Strings special treatment so that they appear to behave like a value type. That is a language feature, and not an operator overload. Operator overloading is language independant.

mskeel said:
When I see an equals operator between two variables, I assume that you are actually checking for equality -- not whether two variables point to the same place in memory.
Are you not a veteran of .Net 1.0/1.1?
 
I understand completely everything that you are saying. I think that your perspective is interesting, but I do not share your opinions. The whole point here is that you are overloading the equals operator, not rewriting. Its not like == suddenly goes away for your object as an Object just becuase you wrote an equals method that (in languages such as C++) is completely sane and normal becuase it determines the actual equality of two objects -- the data in those objects which is what makes them equal.

By the way, the C++ analogy fits just fine. You can always use the address of operator (&) to get the address of a pointer and do a check to determine if the memory locations are the same.

I guess I just dont use == the same way you guys do. I dont have any of the problems of concerns that you have expressed. From everything youve said so far, I dont see anything wrong with using == to actually check for equality in classes. I was afraid that there was some usage standard that I didnt know about or some crazy thing that makes it not good to do, but so far all Ive seen are opinions that I just dont agree with. Not a big deal. Thanks for the debate.
 
Maybe I am confused by my C++ background but, if you dereference the pointers and check the values for equality whats the big deal?
How does this apply to C#?

Let me explain, again, why this analogy is not valid. I know you can use the address of operator and compare addresses, in C++. My whole point is that there is no equivalent to this in C#; no address of operator, and no pointers, hence your analogy is not whole. In other words, using the general "A is to B as C is to D" representation of an analogy, Pointer comparison is to C++ as what is to C#? Object.Equals and Object.ReferenceEquals are not the same.

The closest thing is the C# default == operator, which compares pointers, inline, with no function call. If anything, your C++ background should compel you to leave == alone so that you could have an operator to compare pointers without unnecessary casting or function calls.


And, hey, to each his own. If you like to overload ==, fantastic. By all means, do. It is simply frustrating when you seem blind to the merits of leaving it as it is, and to the difference between adding function and modifying function.
 
Kruger and Epley dug deeper to uncover the reasons behind e-mailers overconfidence. They suspected that it might be because e-mailers assume that other people have the same inside information about their intentions and motivations that they do
 
I didnt really want to revisit this because I felt this thread had taken a turn for the worse (like an awkward lovers quarrel everybody cleared the room for), but then I thought about the Is operator which is used to determine if two object references refer to the same object and thought it might be worth continuing this discussion. Why would you use the equals operator when you could use the Is operator for Visual Basic?

The bottom line with regards to overloading the equals operator seems to come down to personal preference, but I just thought that it was interesting that there are three ways to determine if two objects reference the same memory: Objects equals operator, Object.ReferenceEquals, and Is. From my perspective, I also think that it is odd that with three alternatives, two of which are specifically created with the intention reference equality (one which exists in both VB and C#), that it is necessary to preserve Objects equals operator for checking reference equality.

I apologize if I confused things earlier when talking about pointers. C++ was the language I initially learned to program with so a lot of the metaphors I have to describe programming concepts are in terms of C++. I can see how it makes for a confusing conversation when metaphors dont align. For me, deep vs. shallow (i.e. value vs. reference) is something you always have to be aware of and continuously make conscience decisions about as you code. Its just the way I think about things and it make sense for me.

(And the take home message of the psycology article is that I only have a 50% chance of guessing your tone, marble_eater. Egocentrism refers to the writers assumption that the reader will understand the writers tone and intention just by reading the text. I know you are being specific, but please be a little more careful about your tone when youre posting -- I cant see your body language.:) Some free advice -- "I Statements" go a long way to reducing friction and potential confrontation by allowing others to have options. Im sure youve seen the matrix -- its the same principle.)
 
If the VB "Is" operator is like C#s "is", then that only compares types. Keep in mind that Im a VB.NET noob :)

If I may summarize for those coming in late to the thread:
the topic of whether or not its a good idea to overload Equals or == comes down to two opinions: when you see a comparison of two reference types, would you naturally assume youre comparing the references or the values. In easy terms: do the variables point to the exact same chunk-o-memory or do they both reference the same "value" such as Customer # 123.

I wouldnt presume to say that one is wrong versus the other as long as everyone on the team knows the standard. I would strongly suggest NOT mixing the two (some reference types compare by reference, some reference types compare by value) as youll easily get confused.

I would be curious to see how each of you feels about this subject in a year or so - if anyone has changed their mind. I wish there was a way to flag it to be "reopened" in a year :)

-ner
 
I created a quick and dirty program to test it out to make sure it all worked and the Is operator does indeed check for reference (thats why "someObj Is Nothing" is used instead of = nothing)
Code:
        Dim super As New ArrayList
        Dim duper As ArrayList = super
        Dim wicked As New ArrayList
        Dim awesome As ArrayList = wicked
        
        super.Add(1)
        super.Add(2)
        duper.Add(3)
        MsgBox("super.cout = " + super.Count.ToString()) 3
        MsgBox("duper.cout = " + duper.Count.ToString()) 3
        MsgBox("is super the duper? " + (super Is duper).ToString()) true
        
        wicked.Add(1)
        wicked.Add(2)
        awesome.Add(3)
        MsgBox("wicked.cout = " + wicked.Count.ToString()) 3
        MsgBox("awesome.cout = " + awesome.Count.ToString()) 3
        MsgBox("is wicked the awesome? " + (wicked Is awesome).ToString()) true
        
        MsgBox("is wicked the super? " + (super Is wicked).ToString()) false
        MsgBox("is awesome the super? " + (super Is awesome).ToString()) false
        MsgBox("is wicked the duper? " + (duper Is wicked).ToString()) false
        MsgBox("is awesome the duper? " + (duper Is awesome).ToString()) false
So, in my opinion, at least in VB, I would say that use of the = operator to check whether two objects reference the same memory would be a bad practice becuase you have the Is operator, though I am unsure of the intention of the implementation of Objects = operator.

Now, in C#, things are a little different and it does make talking about .Net in general difficult becuase, at least as far as I know, there isnt anything like the Is operator for C#. At first I thought this was strange, but you can go into unsafe mode in C#, so maybe thats the reasoning behind it? Ive never really done anything "unsafe" in C# before so Im not really sure how deep it allows you to go.

I guess the bottom line, though, as Nerseus pointed out, so long as you are consistant and everyone you deal with understands you then there really isnt a wrong way to do it. So long as you are consistant and well documented, overload away -- there really isnt a convention or best practice (at least as far as I can tell).
 
Perhaps this is why our opinions on the subject are so different.

In VB there are two operators: Is and =. Forgetting overloading for the time being, = performs comparisons on elementary types and Is performs comparisons on object references (= can not compare references). In Visual Basic you have two distinct operators to perform two distinct functions: value comparison and reference comparison.

In C# there is only ==. == compares values. == compares references. In .Net 1.x, there was never an object that allowed for both value comparison and reference comparison, so it seemed to make sense to only have one equality operator. It was either an elementary type or an object, so which type of comparison we want to make is implied. In .Net 2.0, we introduce operator overloading, which allows value comparison in addition to reference comparison, and it can become difficult to tell which one the == operator is going to do.

So, Nerseus and I, primarily C# users, object to overloading == on reference types because it can confuse us, and you, a VB user, dont object because in the world of VB there is no ambiguity.

(Charts are fun)
Code:
Comparison of VB and C# operators

===================================
VB: = Operator
===================================
Type            Value     Reference
-----------------------------------
Elementary:     UnBoxed
Struct:       Overloaded
Class:        Overloaded

===================================
VB: Is Operator
===================================
Type            Value     Reference
-----------------------------------
Elementary:                 Boxed
Struct:                     Boxed
Class:                      Always



===================================
C#: == operator
===================================
Type            Value     Reference
-----------------------------------
Elementary:    UnBoxed     Boxed   (Must either be boxed or unboxed.)
Struct:      Overloaded    Boxed   (May be overloaded or boxed, but not both.)
Class:       Overloaded    Always, unless overloaded (our ambiguity)



So everyone is right. The only issue I can see with overloading = in VB is when the compiled code might be used by a C# coder.
 
One nitpicky point, operator overloading is only new to Visual Basic in .Net 2.0. Its been around in C# since the beginning.


marble_eater said:
The only issue I can see with overloading = in VB is when the compiled code might be used by a C# coder.
That is an interesting point. I wonder if there is a disparity similar to VBs parameters with default values? Ill have to do some more research to see how it all works. I do know that when you overloaded == in C# .Net 1.1, it (=) did not carry over to VB, but you were required to also overload the Equals method for objects (bool Equals(object)). In .Net 2.0, it seems like the overload carries over. So whatever was missing in 1.1 is in 2.0 now, though Im unsure what it looks like at the IL level or even what the Is operator would look like, say in a method in reflector that you write in VB and observe in C#. Im not really sure about the reverse because I usually do my library coding in C# and use it in VB winforms.

Now, the convention that we take when implementing a library in C# is to make == a deep equality operator, the bool Equals(object o) simply returns base.Equals(o) (objects Equals), and you also make an overload of bool Equals() which takes as an argument the type of the class you are creating and simply returns the results of the newly overloaded == operator (to play nice with VB). Thats covered us pretty well and doesnt remove any functionality (though you can always cast to object if you have to use == for something that way -- which is what I usually do because Im used to == for everything from my C++ days and forget about Equals methods). That is the convention that we use, though there may be a better way to go about things or a more widely accepted practice that we havent figured out yet or dont know about.

Im going to research the VB/C# thing a little but if anyone knows the answer or gets to it first, please post.
 
As far as the IL level, The VB Is and C# == (reference comparison) are the same: they load the operands pointers on the stack and use the ceq (compare for equality) instruction. As a matter of fact, since value comparison and reference comparison both break down to the ceq instruction, it makes sense to use == for both. That is, until == is overloaded and can break down to either a function call or a ceq instruction.
 
marble_eater said:
That is, until == is overloaded and can break down to either a function call or a ceq instruction.
There has to be some way to remove the ambiguity or it wouldnt be possible to do the overload in the first place, right?
 
Technically, there is no ambiguity. If an overload exists, the function is called. If not, references are compared. But I am not a computer, and I cant remember every overload that exists. The ambiguity is the human factor, not a logical conflict. It comes into play when I go to see if two of my ListViewItem derived objects are the same object so that I may avoid referencing it twice in my ArrayList, but someone else overloaded the == operator to compare values instead of references so I remove the duplicate entries that I want instead of the duplicate references that I dont. The compiler knows what to do. The programmer is confused.
 
Sorry. I misunderstood what you were trying to say but I think Ive got it now. The "ambiguity" is removed at compile time, because an ambiguity never existed in the first place. I was thinking inclusive or for some reason. No idea why, though.:)

I did some checking on a few things that were bugging me a little, such as the "irregular" equality operators on Strings not being due to operator overloading. It turns out that there isnt some kind of "compiler magic" going on for value types versus reference types that was alleged earlier. It actually is operator overloading that allows the string class to act like a value type instead of a reference type. Check it out in reflector:

C#:
public static bool Equals(string a, string b)
{
      if (a == b)  //this looks kind of funny, because it seems like a circular reference.  This is actually just reflector flipping out a little.  This is a reference check to make sure you arent attempting to compare the same object before you go through a potentially lengthy character comparison.  Sadly, I am not sure of the actual code the developer used to make this check because it seems to of been scrubbed away in the translation process.
      {
            return true;  //this catches them both being null or same memory
      }
      if ((a != null) && (b != null))
      {
            return string.EqualsHelper(a, b); //this method does a character by character comparison just like looping through a char* by hand in C++
      }
      return false;
}

public static bool operator ==(string a, string b)
{
      return string.Equals(a, b);  //some good, DRY code.  Call the static equals method.  This is apparent in the IL with a call statement.
}

 //This is the real kicker that makes string a value type -- how the Equals(object) method is handled
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
public override bool Equals(object obj)
{
      string text1 = obj as string;  //first cast the object to a string
      if ((text1 == null) && (this != null))  //check your references to make sure you can do a value check without throwing a silly exception that doesnt make sense, though the this check for null doesnt really make sense.
      {
            return false;
      }
      return string.EqualsHelper(this, text1); //it actually does a value check here
}

Because reflector sometimes messes the reverse engineering up because a statement can sometimes be interpreted several ways, it is helpful to look at things through different views to make sure you get the full meaning. I am fairly confident the IL will give the definitive solution (duh, because thats what the assembly is in when reflector gets it...:) ) but I cant read IL very well so for the purposes of this conversation, well look at VB.

Code:
Public Shared Function Equals(ByVal a As String, ByVal b As String) As Boolean
      If (a Is b) Then  as you can see it is a simple reference check
            Return True
      End If
      If ((Not a Is Nothing) AndAlso (Not b Is Nothing)) Then  same thing here to prevent a null reference exception before the big comparison
            Return String.EqualsHelper(a, b)
      End If
      Return False
End Function
So, in summary, the String Class is considered a value type because it was instructed to act like a value type, not because there is something special going on behind the scenes. This is great news because it means everything is internally consistent and that there isnt some kind of magical hand waving that goes on for value vs reference classes. It also means that you too can create your very own value types -- its as simple as overloading a few operators/methods the correct way.

The one thing that is still kind of confusing then is, how does this all come together if everything is really a reference type and value types are just implemented operators in a special way? Deep down inside there actually primitives that are so abstracted away from us that we never have to worry about them (unless you want to), and as far as we are concerned, they dont exist. What really makes string a value type more than anything else is the actual check for equality that is done in the EqualsHelper() method.
C#:
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
private static unsafe bool EqualsHelper(string strA, string strB)
{
      int num1 = strA.Length;
      if (num1 != strB.Length)
      {
            return false;
      }
      fixed (char* text1 = ((char*) strA))
      {
            char* chPtr1 = text1;
            fixed (char* text2 = ((char*) strB))
            {
                  char* chPtr2 = text2;
                  char* chPtr3 = chPtr1;
                  char* chPtr4 = chPtr2;
                  while (num1 >= 10)
                  {
                        if ((((*(((int*) chPtr3)) != *(((int*) chPtr4))) || (*(((int*) (chPtr3 + 2))) != *(((int*) (chPtr4 + 2))))) || ((*(((int*) (chPtr3 + 4))) != *(((int*) (chPtr4 + 4)))) || (*(((int*) (chPtr3 + 6))) != *(((int*) (chPtr4 + 6)))))) || (*(((int*) (chPtr3 + 8))) != *(((int*) (chPtr4 + 8)))))
                        {
                              break;
                        }
                        chPtr3 += 10;
                        chPtr4 += 10;
                        num1 -= 10;
                  }
                  while (num1 > 0)
                  {
                        if (*(((int*) chPtr3)) != *(((int*) chPtr4)))
                        {
                              break;
                        }
                        chPtr3 += 2;
                        chPtr4 += 2;
                        num1 -= 2;
                  }
                  return (num1 <= 0);
            }
      }
}

Boom. Unsafe code, just as I would have expected there to be in the deep dark catacombs of .Net, the beams that hold the whole thing together. Here, you actually have access to the pointer and the thing it points to. This method effectively makes String a value type. Its not a value type because theres anything magical going on or it is just special, its a value type because it was designed to be that way.

I did exaggerate slightly above with regards to making your own value type. There is something that makes the string class special in that when you call string equality in your own methods, the ceq is invoked in the IL, where as it is not when I write my own equality operators (even though those operators eventually check value based on value types such as strings and ints. Maybe its the serializable attribute? Maybe there is something else? And then again, maybe there really is some kind of additional magic going on behind the scenes for Strings as every other value type I was able to find with this equals style implementation was a struct. (Maybe I should spawn a new thread about the string equals? Unless I missed something, it does seem kind of strange?)

So, the bottom line here is that as long as you are actively deciding why you implement something in a certain way and make sure that it is well documented, then it is perfectly legit to overload == or not -- it all depends on your wants and needs. If its ok with you, Id like to officially consider the operator equals overload matter closed and just agree to disagree. We can chalk it up to differences in style as it seems neither way is really incorrect or wrong.:)

> /handshake marble_eater
> mskeel extends his hand in friendship to marble_eater
 
In VB7, there was no operator overloading. = Did not call the Equals method, even when overridden, yet you could use = on strings to test for equal value. In VB7, = is translated internally as a VB string comparison function (which happens to use String.Equals), hence, my irregular behavior for a "Magic" type. In .Net 2, the string class actually has operator overloads.

Perhaps on a simple class which more represents a value than an object (for instance, a string), operator overloads may be appropriate, provided that the people using the code are aware. Im just worried about the guy who downloads sample code or a .dll to use, misses the line where you mention that == is overridden, and pulls his hair out trying to figure out why his program is broken. I just code with the convention that overloading == is bad. But, hey, what it always come down to is do what works for you.
 
marble_eater said:
In VB7, there was no operator overloading.
100% true. In my in-depth analysis of the quirky-ness of the String Class I forgot to look at .Net 1.1 (ayee!). I think everything makes sense for me now and I can see why overloading equals was just crazy in that context. I wish I would have had this conversation a few years ago when I firt picked up .Net. Meanwhile, .Net 2.0 brought VB and C# a little closer together, so a lot of these oddities were resolved. Regardless, String still seems to be the outlier.

Im sorry I was such a pain in the neck for a while there, but I think it was a great discussion. Thanks, guys.
 
Back
Top