24

Is it possible to use an object as a key for a Dictonary<object, ...> in such a way that the Dictionary treats objects as equal only if they are identical?

For example, in the code below, I want Line 2 to return 11 instead of 12:

Dictionary<object, int> dict = new Dictionary<object, int>();
object a = new Uri("http://www.google.com");
object b = new Uri("http://www.google.com");

dict[a] = 11;
dict[b] = 12;

Console.WriteLine(a == b);  // Line 1. Returns False, because a and b are different objects.
Console.WriteLine(dict[a]); // Line 2. Returns 12
Console.WriteLine(dict[b]); // Line 3. Returns 12

The current Dictionary implementation uses object.Equals() and object.GetHashCode() on the keys; but I am looking for a different kind of dictionary that uses the object's identity as a key (instead of the object's value). Is there such a Dictionary in .NET or do I have to implement it from scratch?

  • 3
    "object identity" is not a term in .NET - in fact your language is reversed: `a` and `b` are different objects (with different memory-addresses) but with the same *value* - your description implies you do actually want to use the key's value, not their memory-address. – Dai Aug 11 '16 at 05:03

5 Answers5

31

You don't need to build your own dictionary - you need to build your own implementation of IEqualityComparer<T> which uses identity for both hashing and equality. I don't think such a thing exists in the framework, but it's easy enough to build due to RuntimeHelpers.GetHashCode.

public sealed class IdentityEqualityComparer<T> : IEqualityComparer<T>
    where T : class
{
    public int GetHashCode(T value)
    {
        return RuntimeHelpers.GetHashCode(value);
    }

    public bool Equals(T left, T right)
    {
        return left == right; // Reference identity comparison
    }
}

I've restricted T to be a reference type so that you'll end up with objects in the dictionary; if you used this for value types you could get some odd results. (I don't know offhand how that would work; I suspect it wouldn't.)

With that in place, the rest is easy. For example:

Dictionary<string, int> identityDictionary =
    new Dictionary<string, int>(new IdentityEqualityComparer<string>());
Ry-
  • 218,210
  • 55
  • 464
  • 476
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    I recently built a similar construct although I used [object.ReferenceEquals](http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx) within the Equals method as I wanted to check for reference equality even if the `==` operator had been overloaded (which may then call an overridden Equals method for the object). – James Shuttler Jan 20 '12 at 19:32
  • 4
    @JamesShuttler: Even if `==` is overloaded by the *actual* `T` at execution time, the constraint here doesn't force `T` to be something which is overloaded, so this *will* be reference equality. (Try it with string, for example...) – Jon Skeet Jan 20 '12 at 19:33
17

Of course the other answers are entirely correct, but I wrote my own version to suit my needs:

/// <summary>
/// An equality comparer that compares objects for reference equality.
/// </summary>
/// <typeparam name="T">The type of objects to compare.</typeparam>
public sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T>
    where T : class
{
    #region Predefined
    private static readonly ReferenceEqualityComparer<T> instance
        = new ReferenceEqualityComparer<T>();
    /// <summary>
    /// Gets the default instance of the
    /// <see cref="ReferenceEqualityComparer{T}"/> class.
    /// </summary>
    /// <value>A <see cref="ReferenceEqualityComparer<T>"/> instance.</value>
    public static ReferenceEqualityComparer<T> Instance
    {
        get { return instance; }
    }
    #endregion

    /// <inheritdoc />
    public bool Equals(T left, T right)
    {
        return Object.ReferenceEquals(left, right);
    }

    /// <inheritdoc />
    public int GetHashCode(T value)
    {
        return RuntimeHelpers.GetHashCode(value);
    }
}

Design rationale:

  • The class is sealed.

    If the class is not designed to be extended, I'm going to avoid all that expense by sealing it.
    Eric Lippert

    I know many people (including myself) who believe that classes should indeed be sealed by default.
    Jon Skeet

  • There is an Instance static read-only property to expose a single instance of this class.
  • It uses Object.ReferenceEquals() instead of == because ReferenceEquals is more explicit.
  • It uses RuntimeHelpers.GetHashCode() because I don't want to use the possibly overridden GetHashCode of the object, which may not match the behavior of ReferenceEquals. This also avoids a null-check.
  • It has documentation.
Community
  • 1
  • 1
Daniel A.A. Pelsmaeker
  • 47,471
  • 20
  • 111
  • 157
  • I usually find it more of a 'crime' to have a public class then a non-sealed class. Very few classes end up being part of the desired public API: and unless it needs to be public, keep it internal (to the project)! – user2864740 Nov 18 '16 at 22:31
4

Use your own equality comparer

public class ObjectIdentityEqualityComparer : IEqualityComparer<object>
{
    public int GetHashCode(object o)
    {
        return o.GetHashCode();
    }

    public bool Equals(object o1, object o2)
    {
        return object.ReferenceEquals(o1, o2);
    }
}

Note that GetHashCode can be overridden, but the crucial check is made with Equals.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • 1
    I think you mean overridden, but even so, it would be better to use `RuntimeHelpers.GetHashCode` for efficiency. (Any reason for using explicit implementation for half of it but not the other, by the way?) – Jon Skeet Jan 20 '12 at 19:42
  • 1
    I used Microsoft's implementation of `MS.Internal.ComponentModel.ReferenceEqualityComparer`. I also wonder why they mixed implicit/explicit implementation. I changed my code to be more consistent. – Olivier Jacot-Descombes Jan 20 '12 at 19:48
3

As of 5.0, ReferenceEqualityComparer now ships with the runtime.

Kyle McClellan
  • 664
  • 7
  • 23
0

Use Dictionary with IEqualityComparer<TKey> comparer

Kirill Polishchuk
  • 54,804
  • 11
  • 122
  • 125