2

I have two arrays of KeyValuePair<string, object>. I am comparing the two arrays, but noticed that when the value of the KeyValuePair is a value type cast to an object, like object{int}, two pairs with identical keys and value values are not considered equal. However, when a reference type is cast to a value, like object{string}, two pairs with identical key and value values are considered equal.

I discovered this problem when I was using the .Except() method on the two arrays, and noticed although they were identical only the pairs with string values were removed by the set difference. From what I've seen doing research on the equality comparer for KeyValuePair, equality is by value, and not by reference. Curiously, the reference for the pairs with string values should be different anyways.

// array1 and array2 contain the same keys with identical values here

var array1 = GetOldItems(); // This returns a KeyValuePair<string, object>[]
var array2 = GetNewItems(); // This returns a KeyValuePair<string, object>[]
var diff = array1.Except(array2); // The difference is all pairs with non-string value

diff ends up being all pairs in array1 with a non-string value, even though all pairs in array1 had identical keys and values to the pairs in array2.

EDIT: Here is the value of array1 and array2 as reported in the debugger:

{System.Collections.Generic.KeyValuePair<string, object>[5]}
[0]: {[Prop1, 1]}
[1]: {[Prop2, A]}
[2]: {[Prop2, B]}
[3]: {[Prop3, 3]}
[4]: {[Prop1, 2]}

Prop2 has keys where type of value is object{string} and the other pairs have values with type of object{int}.

All pairs for Prop1 and Prop3 remain in the set difference, while all pairs with Prop2 have been removed.

pavuxun
  • 411
  • 2
  • 15
  • An example of arrays with data that are failing would be helpful. – Magnus Feb 13 '19 at 14:57
  • @Magnus just added failing data – pavuxun Feb 13 '19 at 15:01
  • A debugger view doesn't really tell us anything more. What is `A` here? What is `1` here? Also, it's really the contents that were started with that matter. – Jon Hanna Feb 13 '19 at 15:02
  • 1
    I tested it with two equal arrays containig `new KeyValuePair[] { new KeyValuePair("Prop1", 1), new KeyValuePair("Prop2", "A"), new KeyValuePair("Prop2", "B"), new KeyValuePair("Prop3", 3), new KeyValuePair("Prop1", 2), };` and it works as excpected, I got empty result. – Alexander Feb 13 '19 at 15:12
  • 1
    KeyValuePair does not override the Equals() method. When you box it by casting to (object) then you get Object.Equals(). Which is never true for two boxed values since it compares for object identity. – Hans Passant Feb 13 '19 at 16:16
  • Possible duplicate of [Why does KeyValuePair not override Equals() and GetHashCode()?](https://stackoverflow.com/questions/38250596/why-does-keyvaluepair-not-override-equals-and-gethashcode) – Heretic Monkey Apr 11 '19 at 14:45

1 Answers1

1

KeyValuePair uses the default approach to struct equality. If two KeyValuePair<TKey, TValue> have Key properties that compare as equal for their type's default equality and Value properties that compare as equal for their type's default equality, then they are equal. Otherwise they are not.

I do not find what you report. E.g.:

new[] { new KeyValuePair<string, object>("a", 2) }.Except(
  new[] { (new KeyValuePair<string, object>("a", 2))})

returns an empty sequence, because the contents of the two arrays are identical.

I would wonder if perhaps you have pairs where the values are some reference type that does not override Equals() and so are only considered equal if they are the same object. In other words, that kvp1.Value.Equals(kvp2.Value) would return false.

Jon Hanna
  • 110,372
  • 10
  • 146
  • 251