Add your own alternative version
Stats
124.6K views 23 bookmarked
Posted
18 Apr 2005
|
Comments and Discussions
|
|
|
This all looks great if you are working with objects only. But if you implement an overloaded operator == on a type which inplements an interface, then that operator will not be called on type instanciated with the Interface as type. Example'
public interface ITestClass {}
public class TestClass : ITestClass
{
public static bool operator ==(ITestClass x, TestClass y)
{
......
}
}
static void Main(string[] args)
{
ITestClass t1 = new TestClass();
ITestClass t2 = new TestClass();
if( tc1 == tc2 )
Debug.WriteLine("Equal");
else
Debug.WriteLine("Not equal")
}
This will always result in "Not equal", since the operator == on the object is NOT called, instead it will call the Object.operator ==
If anyone has a solution for this, I would really like to know.
Søren
|
|
|
|
|
is a perfect example of why operator overloading is a bad idea:
- The complications are not worth the little bit of syntactic sugar that you gain.
- Implementing it correctly can be complex, leading to bugs
- It's hard for others to read your code - you need a global view of the code before you know what operators do.
- Language supplied operators should be atomic or at least simple -- who needs "[]" to format their hard-drive? Or at least, in a code review, if I see for( i...) { x=y[i] } I don't expect thousands of cycles to be spent in "[]".
- There are only a limmited # of operators to overload with obvious symantics anyway - so you may as well start with descriptive names in the first place, rather than "==" have one meaning and then "MyEquals" doing the next.
Of all the operators, "[]" is the only one I've overriden -- but is x[7] really so much better than x.At(7) that it's worth any risks? Infix operators such as == are a little prettier than x.Equals(y) -- but I think == should be a primitive and always mean the same thing (object equivilence). I would rather have seen the designers of C# provide an operator for object equality ("===" and "!==" let's say) and have that operator be overloadable.
I for one am glad that C# is not C++.
Just my $0.02...
thanks
|
|
|
|
|
The Object.Equals helper static method can be used instead of
public static bool operator==(Base b1, Base b2)
{
if( object.ReferenceEquals( b1, b2 ) )
{
return true;
}
else if( object.ReferenceEquals( b1, null ) ||
object.ReferenceEquals( b2, null ) )
{
return false;
}
return b1.Equals(b2);
}
eg:
public static bool operator==(Base b1, Base b2)
{
return Object.Equals(b1, b2);
}
According to my Applied .Net book it does exactly what you were doing: a reference comparison, comparison to null and then called the virtual .Equals().
|
|
|
|
|
I am fiddeling myself with the problem of treating reference types as if they have value semantics.
I came to the conclusion that maybe one should avoid testing for equality with operator==, since the CLR is expecting this to test for object identity for reference types.
When you implement an equality check for reference types by calling the Equals() methods you get ambiguities like:
class Program
{
public static void Main()
{
Base b1 = new Derived(10, 11);
Base b2 = new Derived(10, 11);
Console.WriteLine("b1.Equals(b2) : {0}", b1.Equals(b2));
Console.WriteLine("b1 == b2 : {0}", b1 == b2);
object o1 = b1;
object o2 = b2;
Console.WriteLine("o1.Equals(o2) : {0}", o1.Equals(o2));
Console.WriteLine("o1 == o2 : {0}", o1 == o2);
}
}
I find such things confusing.
still contemplating ,
-tafkat
|
|
|
|
|
Hi guys,
Your current implementation of GetHashCode looks like this:public override int GetHashCode()
{
return GetHashCode();
} Are you sure this is your final answer??
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
|
|
That teaches me to read over an article when should be sleeping.
That should be:
public override int GetHashCode()
{
return base.GetHashCode();
}
James
|
|
|
|
|
Unfortunately, this won't do. The documentation[^] states:
If two objects of the same type represent the same value, the hash function must return the same constant value for either object.
This means that, if two objects compare equal, they must have the same hash value. Otherwise, for example, several collection classes (like hash tables, dictionaries) will produce unpredictable results. This is important even if you don't use those collections directly. For example, serialization uses a hashtable, and may fail if you don't implement GetHashCode correctly.
The base implementation of GetHashCode is reference based, and may return different hash values for two objects with the same value. What you need is something like:public override int GetHashCode()
{
return x_value;
} for Base and something similar for Derived .
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
|
Nish is modifying the article now to use the hashcode values returned by x_value and y_value for both classes.
Unfortunately this leaves us in a bad prediciment. According to the contract between the Equals and GetHashCode methods altering either one essentially means that the class should be immutable...or at least the data contained within the class that determines the hash code and equality must be immutable.
Luckily there are alternatives like (the soon to be obsolete) IHashCodeProvider and the new IEqualityComparer which could be used in cases where having a custom Equals method is needed for regular code but a poor choice when dealing with hash tables and dictionaries.
I can only hope that the built-in serializers rely on IHasCodeProvider and Object.ReferenceEquals when dealing with the object graph or there are a lot of messed up serialized object graphs out there.
OT: After looking at your blog I thought you might enjoy this: http://mtaulty.com/blog/archive/2005/04/19/1876.aspx[^]
James
|
|
|
|
|
You've hit on what I believe is a rather fundamental design assumption of the .NET framework: types with value semantics are immutable.
On the plus side: you only have to ensure immutability under certain conditions, for example while the object is used as a key in a hashtable, or during serialization (when it's a good idea anyway).
This is fine for value types, because they are small and (mostly) allocated on the stack. Creating a new instance every time a value is modified is no big deal.
But what if you have large objects? Well, we all know strings are immutable, and we all know we should use a StringBuilder to construct a string out of many pieces. A StringBuilder is essentially a mutable string. When you call ToString() on it, you're saying: I'm done changing it - please make it immutable now.
While this scenario works for strings, which aren't usually modified once they've been created, it breaks down for things like vectors, which may be modified inside loops inside loops inside loops.
This is my main reason for believing it's a mistake on the part of the C# designers to not allow explicit overloading of compound assignment operators[^]. Now, x += y is rewritten as x = x + y , which always creates a new instance. Depending on the scenario, this may be up to 50 times slower than 'real' compound assignment.
I'm finding it hard to believe that the team that brought us iterators and anonymous delegates is scared of the complexity[^] involved.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
|
Jeffrey Sax wrote:
Are you sure this is your final answer??
Fixed now (hopefully)
Nish
|
|
|
|
|
Good one! I had blogged about this[^] very recently. I was affected by the static behavior, but I couldn't come up with a nice solution, other than using Equals instead of ==. Your solution is perfect, it's simple and elegant! I'm kicking myself for not thinking about this.
Regards
Senthil
_____________________________
My Blog | My Articles | WinMacro
|
|
|
|
|
Senthil
This article idea originated from the discussion we had in your blog entry
Nish
|
|
|
|
|
There is one problem though. As Jeffrey Sax has pointed out, you need to modify Derived's Equals method to check if obj is indeed Derived before proceeding to use the as operator. This piece of code
Base b = new Base();
Derived d = new Derived();
Console.WriteLine(d == b);
will cause a NullReferenceException as b "is" Base (which is checked in operator == of Base) but not Derived.
Regards
Senthil
_____________________________
My Blog | My Articles | WinMacro
|
|
|
|
|
Quite correct, another bug due to the sleeping virus :p
class Derived {
public override bool Equals( object obj )
{
if( !base.Equals( obj ) )
return false;
Derived o = obj as Derived;
if( o == null )
return false;
return (y_value == o.y_value);
}
}
James
|
|
|
|
|
Yeah, we missed that one alright, eh?
Fixed now!
|
|
|
|
|
|
Blast! All fixed now. Thanks Leppie 
|
|
|
|
|
Good and very natural solution.
In the very end, it uses a basic property of
object oriented programming, namely that a virtual function call
in a base class will be executed in the class of the callee not the caller.
But something is still not clear to me from your article
(to admit I'm a C++ programmer not C# programmer)
:Why are virtual overloads not possible in C#?
You say because of possibly dereferencing the null object.
But this could be handled by an exception (or not)?
|
|
|
|
|
Martin Holzherr wrote:
Why are virtual overloads not possible in C#?
The C# specs state that operator overloads need to be static.
Nish
|
|
|
|
|
I think he is asking why the C# spec mandates them to be static?
Regards
Senthil
_____________________________
My Blog | My Articles | WinMacro
|
|
|
|
|
The C# designers are very big on avoiding confusion and keeping things simple. If a feature is at all ambiguous, or if it would be very complex to enforce consistency, they'd rather just avoid it.
Static operator methods cover most scenarios where operator overloading would be used, and are relatively simple to implement. Allowing overloading based on instance methods has several complications:
- It is ambiguous. There are two different ways to obtain the same result. What if you have both a static method and an instance method. Which one takes precedence? What if Base uses an instance method and Derived defines a static method? There are many possible scenarios, and resolving the ambiguities would be very messy.
- It would be hard to enforce consistency. When applying an operator @ to two objects, one of type Base and one of type Derived , it is not clear that baseObject @ derivedObject == derivedObject @ baseObject if the implementation of the latter can be redefined at any point.
For example, using the classes in this article:
Base baseObject = new Base(1);
Derived derivedObject = new Derived(1, 2);
bool test1 = baseObject == derivedObject;
bool test2 = derivedObject == baseObject; In the current implementation, test1 is true, regardless of the value of y_value in derivedObject . Is this the desired behavior? Probably not.
The second equality test throws a NullReferenceException , because the Equals implementation in Derived assumes that obj is also of type Derived ((obj as Derived).y_value ).
One of the rules for implementing the Equals method[^] is that x.Equals(y) must always return the same value as y.Equals(x) . This is not the case here.
Jeffrey
Everything should be as simple as possible, but not simpler. -- Albert Einstein
Numerical components for C# and VB.NET
|
|
|
|
|
You have a point. My immediate thought was why not make operator overloads non static (always). But then, that would prevent things like
class Foo
{
public static Foo operator + (SomeClass b, Foo f) {
public static Foo operator + (Foo f, SomeClass b) {
}
void SomeFunc()
{
Foo f = new Foo(); SomeClass c = new SomeClass();
f = f + c;
f = c + f;
}
If operator overloading was non static, it would mean that SomeClass must overload "+" with Foo as an argument (the reason for friend operators in C++).
And with both static and non static operator overloads, I agree that there can be ambiguity (although C++ programmers will disagree ).
The second argument, I think, is weaker. The inconsistency you specified can be generated even with static methods. How would you code operator == in Base to achieve the behavior you want (checking for y_value in derivedObject) even with static operator overloading? You obviously can't cast down to Derived ! What should be done is Base should do its checking and Derived's operator == must call Base's first before proceeding to execute its own. Static or non-static operator overloading doesn't make any difference here.
The NullReferenceException could have been avoided if there was a check in Derived's Equals method to see if obj is indeed Derived. IIRC, every implementation of Equals must check that before proceeding. Again, it's not because of the virtualness of operator ==.
What if Derived has been coded like this
class Derived
{
public static bool operator == (Derived d, Base b) { }
}
Then test2's value becomes dependent on how Derived implements this i.e "the implementation can be redefined at a latter point". So this also allows
Base b = new Base();
Derived d = new Derived();
Console.WriteLine(b == d);
Console.WriteLine(d == b);
to be different.
My point is that the "inconsistency" exists even if overloading is static.
Regards
Senthil
_____________________________
My Blog | My Articles | WinMacro
|
|
|
|
|
S. Senthil Kumar wrote:
The NullReferenceException could have been avoided if there was a check in Derived's Equals method to see if obj is indeed Derived.
Yep, it's fixed now.
|
|
|
|
|
|
General News Suggestion Question Bug Answer Joke Praise Rant Admin
Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.
|
|