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