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
= new SomeObject();
Benchmark(iterCount, "Overloaded '=='", delegate
{
for (int i = 0; i < list.Length; ++i)
if (list == null)
++equalCount;
});
Benchmark(iterCount, "Overridden 'Equals'", delegate
{
for (int i = 0; i < list.Length; ++i)
if (list.Equals(null))
++equalCount;
});
Benchmark(iterCount, "Object '=='", delegate
{
for (int i = 0; i < list.Length; ++i)
if ((object) list == null)
++equalCount;
});
Benchmark(iterCount, "RefEquals", delegate
{
for (int i = 0; i < list.Length; ++i)
if (object.ReferenceEquals(list, 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---