I hit a precarious situation in one of my C# projects. I needed to compare various objects, mostly value types., such as int, long, UInt16, sometimes DateTime etc. The problem was I didn't know their actual types until runtime. So naturally, I typed everything as object (they got boxed in) and hit the first problem with the plain equality == operator. And I needed all the comparisons, such as greater then, lower than or equal, whatever.
object o1 = (int)5;
object o2 = (int)5;
if (o1 != o2) { Console.WriteLine("No, they are not"); }
The result should be clear to you. As both o1 and o2 are boxed into object, the variables are actually reference type and not value type by themselves. The original value types are only pointed to by the objects. So if you compare them, you compare references instead of the actual values. And the pointers are naturally different.
Ok. That could be solved, at first sight easily, at the very second not so much.
object o1 = (int)5;
object o2 = (int)5;
if (o1.Equals(o2)) { Console.WriteLine("Yes, this goes well"); }
o1 = (long)5;
o2 = (int)5;
if (o1.Equals(o2)) { Console.WriteLine("Not anymore though"); }
Not so much, because if the two boxed values are not of the very same type, they do not compare. The only viable solution I was able to devise was to Convert.ChangeType() them into a common type and compare. Such as in the following code snippet. Still, this is not a good aproach:
object o1 = (int)5;
object o2 = (int)5;
if (o1.Equals(o2)) { Console.WriteLine("Yes, this goes well"); }
o1 = (long)5;
o2 = (int)5;
if (o1.Equals(Convert.ChangeType(o2, o1.GetType()))) { Console.WriteLine("Uff, I have made it"); }
o1 = (int)5;
o2 = long.MaxValue;
if ((int)5 == long.MaxValue) { Console.WriteLine("While this works well on value types"); }
if (o1.Equals(Convert.ChangeType(o2, o1.GetType()))) { Console.WriteLine("... we are nowhere again with the boxed friends"); }
There were generally any two types comming that could have actually been compared if they were value types. But if you have them boxed, you would have to call the Convert.ChangeType() method on the smallest of the two types. Which would be a pain to determine. Not mentioning, that you can also have negative numbers, then how do you ChangeType() and compare them with unsigned types? Such as one being 18446744073709551615 (UInt64.MaxValue) and the other being -1. Which type would you convert to now?
Convert.ChangeType() is not the right way. It also does not solve problem with other operators anyway. There is nothing else than the Equals() method on object type.
The best aproach to comparing boxed value types seems to me using the dynamic keyword. Look a this:
object o1 = (int)5;
object o2 = (long)5;
if ((dynamic)o1 == (dynamic)o2) { Console.WriteLine("Works like charm"); }
o1 = (int)-1;
o2 = long.MaxValue;
if ((dynamic)o1 < (dynamic)o2) { Console.WriteLine("Anything you want"); }
The compiler leaves decision for runtime and every operator works fine. Naturally, you cannot compare signed and unsigned types, but you would not be able to do it with unboxed value types anyway.