Boxed Value Type comparisons
Asked Answered
A

2

13

What i'm trying to achieve here is a straight value comparison of boxed primitive types.

((object)12).Equals((object)12); // Type match will result in a value comparison,
((object)12).Equals((object)12d); // but a type mismatch will not. (false)
object.Equals((object)12,(object)12d); // Same here. (false)

I understand the 'why'. I just don't see a 'how'.

The types are unknown until runtime, where they could be any primitive type from a datasource. That includes strings, datetimes, bools, etc. I've gone down the ugly route of writing an extension method that works out both types, and then casts before doing a '==' comparison: (For completeness, i included every primitive type, plus those i was interested in)

public static bool ValueEquals(this object thisObj, object compare)
    {
        if (thisObj is int)
        {
            int obj = (int)thisObj;
            if (compare is int)
                return (obj == (int)compare);
            if (compare is uint)
                return (obj == (uint)compare);
            if (compare is decimal)
                return (obj == (decimal)compare);
            if (compare is float)
                return (obj == (float)compare);
            <... and so on for each primitive type ...>
        }
        if (thisObj is uint)
        {
            uint obj = (uint)thisObj;
            if (compare is int)
                return (obj == (int)compare);
            if (compare is uint)
                return (obj == (uint)compare);
            <... Again for each primitive type ...>
        }
        if (thisObj is decimal)
        {
            decimal obj = (decimal)thisObj;
            if (compare is int)
                return (obj == (int)compare);
            <... Etc, etc ...>

The resulting method turned out to be 300+ lines long, which was fine (yet hideous), but now I need to do more than just '=='. I need >, <, <=, >=, !=.

Is there anything in Reflection that i could use for boxed value type comparisons?

Anything at all?

Automaton answered 12/7, 2011 at 17:49 Comment(6)
So you just want to be able to compare two UNLIKE boxed values that may contain the same value? You don't want Equals() because most comparissons of unlike types return false. I'd try something like...Territoriality
Use the C# 4.0 dynamic keyword instead.Proffitt
I'd avoid dynamic because you'd be more prone to run-time issues. With the solution below you'd at least know that both types are convertible at compile time and avoid many run-time errors.Territoriality
The dynamic keyword will not compare dynamic types of different type. the == operator throws a runtime exception.Automaton
Although the suggested answers didn't solve my problem, i have managed to figure out a solution using both the answers provided. IConvertible is the key. For equality i now use Convert.ChangeType on both args to return a string and simply ==. For >, <, >=, <= etc, i return a decimal and use the appropriate operator. Nice one!Automaton
Hey, I did add an overload that should hopefully work better for you, check it out in the answer. I know it's already marked answered (thank you) but hopefully does it in a very generic way.Territoriality
T
8

Looks like you are assuming the type from arg1 is the one you want to convert to, so I'd use a genric like this. As long as arg2 is IConvertible (int, double, all numerics, string, etc are all IConvertible) this will work:

public static bool ValueEquality<T1, T2>(T1 val1, T2 val2) 
    where T1 : IConvertible 
    where T2 : IConvertible
{
    // convert val2 to type of val1.
    T1 boxed2 = (T1) Convert.ChangeType(val2, typeof (T1));

    // compare now that same type.
    return val1.Equals(boxed2);
}

** UPDATE ** Made both types generic args, can both be inferred and adds more compile time safety on arg2 to make sure it's IConvertible at compile time.

Given this generic function, all of the following now return true (don't need to specify type argument since inferred from first argument:

        Console.WriteLine(ValueEquality(1, "1"));
        Console.WriteLine(ValueEquality(2, 2.0));
        Console.WriteLine(ValueEquality(3, 3L));

UPDATE

Based on your comment, here's an overload if all you have are objects. Both can co-exist and it will call the one more appropriate based on the arguments:

    public static bool ValueEquality(object val1, object val2)
    {
        if (!(val1 is IConvertible)) throw new ArgumentException("val1 must be IConvertible type");
        if (!(val2 is IConvertible)) throw new ArgumentException("val2 must be IConvertible type");

        // convert val2 to type of val1.
        var converted2 = Convert.ChangeType(val2, val1.GetType());

        // compare now that same type.
        return val1.Equals(converted2);
    }

And this will work for object:

        object obj1 = 1;
        object obj2 = 1.0;

        Console.WriteLine(ValueEquality(obj1, obj2));

As I said, both of these can co-exist as overloads, so if you compare compatible IConvertible types directly it will use the generic, and if you just have boxed types as object, it will use the object overload.

Territoriality answered 12/7, 2011 at 18:17 Comment(3)
Sadly, I only have objects to use as arguments (the boxed value types). This won't work for me.Automaton
Updated with a new overload. You can use both and the compiler will pick the correct one based on explicit IConvertible args or object.Territoriality
Caveat: Let val1 be an int, and val2 be a long that is greater than int.MaxValue. This method will throw an OverflowException instead of returning false, which is semantically what I would expect to happen.Latterday
C
3

Look into using IComparable instead of manual if's - http://msdn.microsoft.com/en-us/library/system.icomparable.compareto.aspx.

If you need something similar in future consider swith on types of one operand first and implementing "operation handler" class for each of the types with method to handle the operation like IntOpHandler.PerformOp(int left, object right).

You can also often decrease number of types you need to deal with by merging multiple types togeter first (i.e. byte, short, ushort, int, uint, long - cast to long first, then perform operations on long).

Coltun answered 12/7, 2011 at 18:18 Comment(1)
This has to be an answer. The question was about using <, >, <= etc. And IComparable interface is giving answer to this.Hanschen

© 2022 - 2024 — McMap. All rights reserved.