Re: Why is concept of equals and operator== implemented this way?
- From: Barry Kelly <barry.j.kelly@xxxxxxxxx>
- Date: Sun, 18 Jun 2006 00:16:41 +0100
cody <deutronium@xxxxxx> wrote:
Barry Kelly wrote:
cody <deutronium@xxxxxx> wrote:
Why not forbid overloading of == and != but instead translate each call
of objA==objB automatically in System.Object.Equals(objA, objB).
'==' is resolved statically, while Object.Equals(object) is determined
by dynamic dispatch. The simplest answer is: performance, both in
textual code size (see below) and in runtime execution.
even if making unrealistic micro benchmarks with empty method bodies
there is almost no difference between normal methods and virtual ones.
Unless it's been overloaded, '==' isn't a method call.
When you call a virtual method, you've got no idea what code is going to
be called. That's one of the reasons why it isn't recommended to call
virtual methods while holding a lock. If things were to work this way,
no one could safely compare object references while holding locks,
unless they used the cumbersome object.ReferenceEquals(object,object)
method.
This sounds logical at first, but in fact, but looking into microsofts
shared source cli implementation, almost all implementations of
operator== is calling Equals() and the very few ones not doing this are
accessing properties of the object which are in some cases also virtual
or calling virtual methods internally.
What you seem to be advocating is to turn *every* *usage* of '==' with
reference types into a method call, but it isn't currently. What you've
been looking up in the SSCLI is the overloaded '==' operator on various
types. The built-in '==' operator for reference types has different
semantics.
Also, it is not always possible to override the correct Equals. If the
two sides of the '==' are of different type, which side would the
compiler generate a call to .Equals for? What if one side is a Framework
type like int, and the other is a complex number type?
Well, I see the problem. But in reality I've never seen an
implementation of operator== for different types. Did you?
Yes. I've implemented them, for my own Date type.
I cannot imagine a scenario where this would be necessary, given the
fact that objects of different types should per definition never be
considered to be equal, and even if, you can implement an implicit
conversion operator for that.
There was a conversion operator too, but why convert when you can
compare as-is?
In conclusion, I still feel that the concepts of ==,!= and equals could
have been implemented in a simpler and more logical way in .NET.
Maybe the current implementation may be 1% faster than the simpler one
and you can do very strange stuff like == and != returning the same
value or making objects of different types equal but if that makes 99.9%
of all normal cases harder to write and to maintain this is too much to pay.
I will submit this contrived micro-benchmark in favour of the current
situation, if only to point out some of the overhead of virtual method
calls on Equals etc.:
---8<---
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Runtime.CompilerServices;
class SomeObject
{
[MethodImpl(MethodImplOptions.NoInlining)]
public override bool Equals(object obj)
{
// This would be the only way to perform reference equality
// checks if the proposed idea was implemented.
return object.ReferenceEquals(this, obj);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool operator==(SomeObject left, SomeObject right)
{
return object.ReferenceEquals(left, right);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static bool operator!=(SomeObject left, SomeObject right)
{
return !object.ReferenceEquals(left, right);
}
[MethodImpl(MethodImplOptions.NoInlining)]
public override int GetHashCode()
{
return base.GetHashCode();
}
}
class App
{
delegate void Method();
static void Benchmark(int iterations, string label, Method method)
{
method(); // warmup
Stopwatch start = Stopwatch.StartNew();
for (int i = 0; i < iterations; ++i)
method();
Console.WriteLine("{0,20} : {1,6:f3} ({2} iterations)",
label,
start.ElapsedTicks / (double) Stopwatch.Frequency,
iterations);
}
static void Main()
{
const int iterCount = 30;
const int objectCount = 10000000;
// To eliminate "unused value" optimizations.
int equalCount = 0;
SomeObject[] list = new SomeObject[objectCount];
for (int i = 0; i < objectCount; ++i)
list[i] = new SomeObject();
Benchmark(iterCount, "Overloaded '=='", delegate
{
for (int i = 0; i < list.Length; ++i)
if (list[i] == null)
++equalCount;
});
Benchmark(iterCount, "Overridden 'Equals'", delegate
{
for (int i = 0; i < list.Length; ++i)
if (list[i].Equals(null))
++equalCount;
});
Benchmark(iterCount, "Object '=='", delegate
{
for (int i = 0; i < list.Length; ++i)
if ((object) list[i] == null)
++equalCount;
});
Benchmark(iterCount, "RefEquals", delegate
{
for (int i = 0; i < list.Length; ++i)
if (object.ReferenceEquals(list[i], null))
++equalCount;
});
Console.WriteLine("Total EqualCount: {0}", equalCount);
}
}
--->8---
On my system:
---8<---
Overloaded '==' : 1.582 (30 iterations)
Overridden 'Equals' : 2.662 (30 iterations)
Object '==' : 0.609 (30 iterations)
RefEquals : 0.896 (30 iterations)
Total EqualCount: 0
--->8---
Of limited applicability, very contrived, usual disclaimers, etc. etc...
-- Barry
--
http://barrkel.blogspot.com/
.
- Follow-Ups:
- References:
- Prev by Date: Re: Why is concept of equals and operator== implemented this way?
- Next by Date: Re: This is annoying
- Previous by thread: Re: Why is concept of equals and operator== implemented this way?
- Next by thread: Re: Why is concept of equals and operator== implemented this way?
- Index(es):
Relevant Pages
|