Comparing Boxed Value Types

Comparing boxed value types

If you need different behaviour when you're dealing with a value-type then you're obviously going to need to perform some kind of test. You don't need an explicit check for boxed value-types, since all value-types will be boxed** due to the parameter being typed as object.

This code should meet your stated criteria: If value is a (boxed) value-type then call the polymorphic Equals method, otherwise use == to test for reference equality.

public void SetValue(TEnum property, object value)
{
bool equal = ((value != null) && value.GetType().IsValueType)
? value.Equals(_properties[property])
: (value == _properties[property]);

if (!equal)
{
// Only come here when the new value is different.
}
}

( ** And, yes, I know that Nullable<T> is a value-type with its own special rules relating to boxing and unboxing, but that's pretty much irrelevant here.)

Comparing boxed values of different types

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!

Boxed Value Type comparisons

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.

How to check equality of boxed object of value types when types are different but compatible to compare the values

The type of the variable is kept in object.

For example:

Console.Write(ob_a.GetType().ToString());   // will give you System.Int32
Console.Write(ob_b.GetType().ToString()); // will give you System.Int16

It gives different hash codes also with method: GetHashCode()

If you will convert short variable to int or the other way, it will be equal... So basically the problem is with different variable types.

Here is the answer for both questions: http://www.ikriv.com/dev/dotnet/ObjectEquality.html

Since call to Equals() is virtual, exact version of the method that will be called by x.Equals(y) is determined by dynamic type of x, that usually is not known at compile time. Note also, that unlike a==b, expression x.Equals(y) is inherently asymmetrical. Only x dictates what version of Equals() will be called. y has absolutely no say in the matter.

When using == for a primitive and a boxed value, is autoboxing done, or is unboxing done

It is defined in the JLS #15.21.1:

If the operands of an equality operator are both of numeric type, or one is of numeric type and the other is convertible (§5.1.8) to numeric type, binary numeric promotion is performed on the operands (§5.6.2).

And JLS #5.6.2:

When an operator applies binary numeric promotion to a pair of operands, each of which must denote a value that is convertible to a numeric type, the following rules apply, in order:

  • If any operand is of a reference type, it is subjected to unboxing conversion
    [...]

So to answer your question, the Integer is unboxed into an int.

How runtime knows the exact type of a boxed value type?

When a value is boxed it gets an object header. The kind that any type that derives from System.Object has. The value follows that header. The header contains two fields, one is the "syncblk", it has various uses that are beyond the scope of the question. The second field describes the type of object.

That's the one you are asking about. It has various names in literature, most commonly "type handle" or "method table pointer". The latter is the most accurate description, it is a pointer to the info the CLR keeps track of whenever it loads a type. Lots of framework features depend on it. Object.GetType() of course. Any cast in your code as well as the is and as operators use it. These casts are safe so you can't turn a Dog into a Cat, the type handle provides this guarantee. The method table pointer for your boxed int points to the method table for System.Int32

Boxing was very common in .NET 1.x, before generics became available. All of the common collection types stored object instead of T. So putting an element in the collection required (implicit) boxing, getting it out again required explicit unboxing with a cast.

To make this efficient, it was pretty important that the jitter didn't need to consider the possibility that a conversion would be required. Because that requires a lot more work. So the C# language included the rule that unboxing to another type is illegal. All that's needed now is a check on the type handle to ensure it is expected type. The jitter directly compares the method table pointer to the one for System.Int32 in your case. And the value embedded in the object can be copied directly without any conversion concerns. Pretty fast, as fast as it can possibly be, this can all be done with inline machine code without any CLR call.

This rule is specific to C#, VB.NET doesn't have it. Typical trade-off between those two languages, C#'s focus is on speed, VB.NET on convenience. Converting to another type when unboxing isn't otherwise a problem, all simple value types implement IConvertible. You write it explicit in your code, using the Convert helper class:

        int i = 123;                    // A value type
object box = i; // Boxing
long j = Convert.ToInt64(box); // Conversion + unboxing

Which is pretty similar to the code that the VB.NET compiler auto-generates.

Comparing boxed values does not behave as expected

String is a reference type.

I don't know how you are creating those strings but they a re probably represented as 2 different instances of string object.

In you method you aare doing a == on 2 objects. This will by default check only if they are the same references.

Why not use Generics and use the Comparer.Default or just use the Equals() with a null check considering your are boxing?

    object a = "d";
object b = new String(new []{'d'});

Console.Write("a == b: ");

Console.WriteLine(a == b);

Console.WriteLine("AreValueEquals: " + AreValueEquals(a,b));

Console.WriteLine("Equals: " + a.Equals(b));

Gives:

a == b: False
AreValueEquals: False
Equals: True

Confirming the intern:

    Console.WriteLine("\r\nComparing 2 constants\r\n");

object c = "d";

Console.Write("a == c: ");

Console.WriteLine(a == c);

Console.WriteLine("AreValueEquals: " + AreValueEquals(a,c));

Console.WriteLine("Equals: " + a.Equals(c));

Gives:

Comparing 2 constants

a == c: True
AreValueEquals: True
Equals: True

Have a look at this fiddle

Boxing type equality and dictionary keys

TLDR: Comparing boxed values using == uses reference equality of the boxing object, but comparing boxed values using Equals() uses the underlying values' Equals().


When a value type is boxed, the boxing object's GetHashCode() and Equals() method implementations call the boxed value's versions.

That is, given:

  • A value type VT that implements GetHashCode() and Equals() correctly.
  • An instance x of VT.
  • An instance y of VT with the same value as x.
  • A boxed instance of x: bx.
  • A boxed instance of y: by.

The following will be the case:

x.Equals(y)      == true             // Original values are equal
bx.Equals(by) == true // Boxed values are equal
x.GetHashCode() == y.GetHashCode() // Original hashes are equal
bx.GetHashCode() == by.GetHashCode() // Boxed hashes are equal
bx.GetHashCode() == x.GetHashCode() // Original hash code == boxed hash code

However, the == operator is NOT delegated by the boxed version, and in fact it is implemented using reference equality, so:

(x == y)   == true  // Original values are equal using "=="
(bx == by) == false // Boxed values are not equal using "=="
ReferenceEquals(bx, by) == false // References differ

The Dictionary is using GetHashCode() and Equals() for comparing objects, and because they delegate to the underlying values it works correctly.


This is demonstrated by the following program:

using System;

namespace Demo
{
struct MyStruct: IEquatable<MyStruct>
{
public int X;

public bool Equals(MyStruct other)
{
return X == other.X;
}

public override bool Equals(object obj)
{
if (obj is not MyStruct other)
return false;

return X == other.X;
}

public override int GetHashCode()
{
return -X;
}
}

class Program
{
static void Main()
{
var x = new MyStruct { X = 42 };
var y = new MyStruct { X = 42 };
object bx = x;
object by = y;

Console.WriteLine(bx.GetHashCode()); // -42
Console.WriteLine(y.GetHashCode()); // -42

Console.WriteLine(bx.Equals(by)); // True
Console.WriteLine(bx == by); // False
Console.WriteLine(object.ReferenceEquals(bx, by)); // False
}
}
}

Dynamic comparison of boxed primitive values

Maybe so

public static int Compare(object value1, object value2)
{
if (value1 is double || value2 is double)
{
double d1 = Convert.ToDouble(value1);
double d2 = Convert.ToDouble(value2);
return d1.CompareTo(d2);
}

if (value1 is float || value2 is float)
{
float f1 = Convert.ToSingle(value1);
float f2 = Convert.ToSingle(value2);
return f1.CompareTo(f2);
}

long x1 = Convert.ToInt64(value1);
long x2 = Convert.ToInt64(value2);
return x1.CompareTo(x2);
}

The byte, short, int types can be converted to long without precision loss.



Related Topics



Leave a reply



Submit