Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's English Pages

:

Engineering and troubleshooting by Directory Master!
MCM: Directory

Quick Launch

Ondrej Sevecek's English Pages > Posts > C# and boxed value type comparison operators with object and dynamic types
October 09
C# and boxed value type comparison operators with object and dynamic types

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.

Comments

very nice

Excellent solution, instead of working with Reflection or long case statements, casting to dynamic is very easy and compact.
 on 22/05/2018 15:32

Works unless one or both of o1 and o2 are not value types

If you have

object o1 = new SomeObject();
object o2 = new AnotherObject();

then your solution will throw the following exception;

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException
Operator '==' cannot be applied to operands of type 'ConsoleApp2.A' and 'ConsoleApp2.B'

The only universal solution I've found is using reflections. Just pass your objects to the following function;

        static new bool Equals(object o1, object o2)
        {
            System.Reflection.MethodInfo method = o1.GetType().GetMethod("Equals", new Type[] { o1.GetType() });
            if (method == null) method = o1.GetType().GetMethod("Equals");
            if (method != null)
            {
                return (bool)method.Invoke(o1, new object[] { o2 });
            }
            else
                return false;
        }
 on 28/09/2018 21:25

One further change

Actually if one of o1 and o2 is a value type and the other is not then my code throws an exception. The following solves that issue;

          static new bool Equals(object o1, object o2)
        {
            if (o1?.GetType().IsValueType.Equals(o2?.GetType().IsValueType) ?? false)
            {
                System.Reflection.MethodInfo method = o1.GetType().GetMethod("Equals", new Type[] { o1.GetType() });
                if (method == null) method = o1.GetType().GetMethod("Equals");
                if (method != null)
                {
                    return (bool)method.Invoke(o1, new object[] { o2 });
                }
                else
                    return false;
            }
            else
                return (o1 is null && o2 is null);
        }
 on 28/09/2018 21:44

Dynamic did it!

Thanks for this, I didn't get the point with dynamic type comparison..
 on 11/09/2019 11:07

Add Comment

Sorry comments are disable due to the constant load of spam *


This simple antispam field seems to work well. Just put here the number.

Title


You do not need to provide any value this column. It will automatically fill with the name of the article itself.

Author *


Body *


Attachments