Dictionary<TKey, TValue> with user-defined class implementingIEquatable<T> as key

  • Thread starter Thread starter craig.wenger
  • Start date Start date
C

craig.wenger

I have a question about using the generic Dictionary class in .NET 2.0
with user-defined classes as the key.

MSDN says (http://msdn2.microsoft.com/en-us/library/xfhwa508.aspx):

Dictionary<TKey, TValue> requires an equality implementation to
determine whether keys are equal. You can specify an implementation of
the IEqualityComparer<T> generic interface by using a constructor that
accepts a comparer parameter; if you do not specify an implementation,
the default generic equality comparer EqualityComparer<T>.Default is
used. If type TKey implements the System.IEquatable<T> generic
interface, the default equality comparer uses that implementation.

However, I wrote a quick test and the part about IEquatable<T> does
not appear true to me.

Consider this code:

class Program
{
static void Main(string[] args)
{
Integer test1 = new Integer(5);
Integer test2 = new Integer(5);
Dictionary<Integer, object> test = new Dictionary<Integer,
object>();
test.Add(test1, null);
Console.WriteLine("test1.Equals(test2): " +
test1.Equals(test2).ToString());
Console.WriteLine("test.ContainsKey(test1): " +
test.ContainsKey(test1).ToString());
Console.WriteLine("test.ContainsKey(test2): " +
test.ContainsKey(test2).ToString());
Console.ReadKey();
}
}

public class Integer : IEquatable<Integer>
{
private int _integerValue;

public int IntegerValue
{
get { return _integerValue; }
set { _integerValue = value; }
}

public Integer(int integerValue)
{
_integerValue = integerValue;
}

#region IEquatable<Integer> Members

public bool Equals(Integer other)
{
if(other == null)
{
return false;
}

if(other._integerValue == _integerValue)
{
return true;
}
else
{
return false;
}
}

#endregion
}

The output is True, True, False. Even though test1.Equals(test2) is
True, a dictionary containing only test1 will say it contains the key
test1 but not test2 (presumably because their hash codes are
different?). Essentially implementing IEquatable<T> does not appear to
change anything with respect to the class' behavior as a key in
Dictionary<TKey, TValue>.

However, when I add code for a class that implements
IEqualityComparer<Integer>:

public class IntegerEqualityComparer : IEqualityComparer<Integer>
{
public IntegerEqualityComparer() { }

#region IEqualityComparer<Integer> Members

public bool Equals(Integer x, Integer y)
{
if(x == null || y == null)
{
return false;
}

if(x.IntegerValue == y.IntegerValue)
{
return true;
}
else
{
return false;
}
}

public int GetHashCode(Integer obj)
{
return base.GetHashCode();
}

#endregion
}

and change the Dictionary constructor call to use it:

Dictionary<Integer, object> test = new Dictionary<Integer, object>(new
IntegerEqualityComparer());

I get True for all three. This makes it seem like the dictionary with
a key of type T is not using IEquatable<T>, it only uses
IEqualityComparer<T> with the appropriate constructor overload.
 
I have a question about using the generic Dictionary class in .NET 2.0
with user-defined classes as the key.

MSDN says (http://msdn2.microsoft.com/en-us/library/xfhwa508.aspx):

Dictionary<TKey, TValue> requires an equality implementation to
determine whether keys are equal. You can specify an implementation of
the IEqualityComparer<T> generic interface by using a constructor that
accepts a comparer parameter; if you do not specify an implementation,
the default generic equality comparer EqualityComparer<T>.Default is
used. If type TKey implements the System.IEquatable<T> generic
interface, the default equality comparer uses that implementation.

However, I wrote a quick test and the part about IEquatable<T> does
not appear true to me.

Consider this code:

    class Program
    {
        static void Main(string[] args)
        {
            Integer test1 = new Integer(5);
            Integer test2 = new Integer(5);
            Dictionary<Integer, object> test = new Dictionary<Integer,
object>();
            test.Add(test1, null);
            Console.WriteLine("test1.Equals(test2): " +
test1.Equals(test2).ToString());
            Console.WriteLine("test.ContainsKey(test1): " +
test.ContainsKey(test1).ToString());
            Console.WriteLine("test.ContainsKey(test2): " +
test.ContainsKey(test2).ToString());
            Console.ReadKey();
        }
    }

    public class Integer : IEquatable<Integer>
    {
        private int _integerValue;

        public int IntegerValue
        {
            get { return _integerValue; }
            set { _integerValue = value; }
        }

        public Integer(int integerValue)
        {
            _integerValue = integerValue;
        }

        #region IEquatable<Integer> Members

        public bool Equals(Integer other)
        {
            if(other == null)
            {
                return false;
            }

            if(other._integerValue == _integerValue)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion
    }

The output is True, True, False. Even though test1.Equals(test2) is
True, a dictionary containing only test1 will say it contains the key
test1 but not test2 (presumably because their hash codes are
different?). Essentially implementing IEquatable<T> does not appear to
change anything with respect to the class' behavior as a key in
Dictionary<TKey, TValue>.

However, when I add code for a class that implements
IEqualityComparer<Integer>:

    public class IntegerEqualityComparer : IEqualityComparer<Integer>
    {
        public IntegerEqualityComparer() { }

        #region IEqualityComparer<Integer> Members

        public bool Equals(Integer x, Integer y)
        {
            if(x == null || y == null)
            {
                return false;
            }

            if(x.IntegerValue == y.IntegerValue)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public int GetHashCode(Integer obj)
        {
            return base.GetHashCode();
        }

        #endregion
    }

and change the Dictionary constructor call to use it:

Dictionary<Integer, object> test = new Dictionary<Integer, object>(new
IntegerEqualityComparer());

I get True for all three. This makes it seem like the dictionary with
a key of type T is not using IEquatable<T>, it only uses
IEqualityComparer<T> with the appropriate constructor overload.

If anybody is curious, my guess is what is happening is that
IEquatable<T> only defines the behavior when the objects have the same
hash code.
 
Back
Top