Comparing boxed values of different types
Asked Answered
K

3

5

In C#, value types can be boxed, which leads to certain comparison issues, specially for different types. Example: 2m == 2L returns true, but (object)2m == (object)2L returns false. My question is: is it possible to write a comparison method that gets two objects (boxed values) parameters and returns true in the example case? It has to work for any value type combination and have the same behavior as the == operator if the values were unboxed. Thanks!

Kalat answered 29/8, 2018 at 13:21 Comment(15)
Simply: object == object checks for reference-equality. So when you box your struct it´ll be compared to another instance of that struct via reference-equality which is never true.Carolinian
Does using object.Equals(object) meet your needs?Amnesia
@HimBromBeere Yes, I know the reason behind it. What I want to know is if there is a way to achieve the original behavior, even if it envolves ugly code.Kalat
@JoeSewell according to the C# interactive, no. ((object)2m).Equals((object)2L) returns false as well.Kalat
I can´t imagine why you needed that. Why even box the values in the first place when you obviously need value-type semantics, not references?Carolinian
You need method with two object type parameters which has comparision of value inside and method must recognize type of value to compare?Lands
If you know that it's something that can be converted to decimal you can use bool equal = Convert.ToDecimal(obj1) == Convert.ToDecimal(obj2);Haig
@TimSchmelter I don't know that. I have a object collection and I need to know if "something" is already there, even if the first thing is a decimal and the second thing is a integer, but with the same value.Kalat
@Lands yes.Kalat
@Logerfo: yes, but it can't be a string or Foo or whatever? It's guaranteed that i's a numeric value?Haig
@TimSchmelter No, it's not guaranteed. It can be anything. When there is no valid == operator (it would be a compilation error if tried), the method should return false.Kalat
agree with @JoeSewell Object.Equals might work hereVincentia
@EhsanSajjad it doesn't, because the left and the right side types are different (decimal and long).Kalat
@Kalat didn't noticed that but why someone would want to do that just wondering..Vincentia
@EhsanSajjad for example, in my database, some quantity fields can only be a natural number, so an integer type is used. Some other quantity fields can assume decimal places, so the decimal type is used. That's easier than always using decimal and creating checks to only allow integer values in certain fields.Kalat
D
7

I suggest to use dynamic for that task.

object o1 = 2m;
object o2 = 2L;

if ((dynamic)o1 == (dynamic)o2) { Console.WriteLine("Works like charm"); }

Yet, I am not fully aware of all implications of dynamic keyword, so take care!

Desultory answered 29/8, 2018 at 13:31 Comment(9)
wouldn't it be better to use dynamic as the data type instead of in a cast? dynamic only states that the field's type can change at runtime...Steric
@Tau: but maybe o1 and o2 are given and can't be changedHaig
It worked. I thought of that, but didn't test it. I always take a bad look at dynamic because of performance issues. I'll definitely use this approach if I don't get a better answer. Thanks!Kalat
@Tau it can be changed, I'll probably do that. Thanks!Kalat
@Logerfo: well, if you can change it why do you convert it to Object at all? Why don't you leave it as the real type? If you are concerned about performance you should not box everything in the first place.Haig
@TimSchmelter my structure is a cache. I have a method with a params object array. It is used generically for several calls, with different arities and types. When called with the same parameters, the cached value should be used. But if the cached value is indexed by 2L and the new call uses 2m, the cache is currently not used, although I want it to be used.Kalat
which overload it will resolve to for == operatorVincentia
@EhsanSajjad o1.GetType() and o2.GetType(). If it does not exist, return false (meaning not equal).Kalat
Just want to warn that there are certain cases where the solution with dynamic will fail. It's basically when there are ambiguous or no == operators exist. For example, consider object o1 = 3UL and object o2 = 3L.Jeroldjeroma
J
1

Since the accepted solution with dynamic may throw an exception when it fails to find a proper == operator (see my comment about comparing 3UL and 3L values), I've implemented another way to check equality.

The code below invokes Equals method if the both boxed values are of the same type, or tries to compare the values by unboxing to a common type. The float and double types have special processing, the rest integer types are compared by converting to decimal.

This method is little bit slower than the accepted solution, but it handles more cases and performs better from memory allocation perspective:

|          Method |      Mean |    Error |   StdDev |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|---------------- |----------:|---------:|---------:|-------:|------:|------:|----------:|
| AreEqualDynamic |  83.31 ns | 1.447 ns | 1.354 ns | 0.0172 |     - |     - |      72 B |
| AreEqualConvert | 112.44 ns | 1.156 ns | 0.902 ns |      - |     - |     - |         - |

The AreEqualConvert method implementation:

public bool AreEqualConvert(object o1, object o2)
{
    if (ReferenceEquals(o1, o2))
    {
        return true;
    }

    if (o1 is null || o2 is null)
    {
        return false;
    }

    if (o1.GetType() == o2.GetType())
    {
        return o1.Equals(o2);
    }

    switch (o1)
    {
        case float f1:
            switch (o2)
            {
                case double d2:
                    return f1 == d2;
                case IConvertible c2:
                    return f1 == c2.ToSingle(null);
                default:
                    return false;
            }

        case double d1:
            return o2 is IConvertible conv2
                ? d1 == conv2.ToDouble(null)
                : false;

        case IConvertible c1:
            switch (o2)
            {
                case float f2:
                    return c1.ToSingle(null) == f2;
                case double d2:
                    return c1.ToDouble(null) == d2;
                case IConvertible c2:
                    return c1.ToDecimal(null) == c2.ToDecimal(null);
                default:
                    return false;
            }

        default:
            return false;
    }
}
Jeroldjeroma answered 10/1, 2020 at 12:17 Comment(0)
L
0

I think using dynamic is best approach to this, other solution may be something like this (Marshall is for casting always to bigger type)

private static bool compareObj(object obj1, object obj2)
{
    bool flag = true;
    try
    {
        object result = Convert.ChangeType(obj1, obj2.GetType());
        object result2 = Convert.ChangeType(obj2, obj1.GetType());
        var first = Marshal.SizeOf(obj1.GetType());
        var second = Marshal.SizeOf(obj2.GetType());
        if (first > second)
        {
             flag = obj1.Equals(result2);
        }
        else
        {
             flag = obj2.Equals(result);
        }
    }
    catch (InvalidCastException ex)
    {
        flag = false;
    }

    return flag;
}
Lands answered 29/8, 2018 at 14:5 Comment(4)
why do you think the dynamic solution is better than yours?Kalat
I tested it and it does not work. I'm sticking with dynamic.Kalat
I change a little, try nowLands
Now it works for almost everything, except for custom classes with overridden Equals method.Kalat

© 2022 - 2024 — McMap. All rights reserved.