2012-01-20 5 views
16

¿Es posible utilizar un objeto como clave para un Dictonary<object, ...> de forma que el diccionario trate los objetos como iguales solo si son idénticos?Cómo utilizar la identidad de un objeto como clave para el Diccionario <K,V>

Por ejemplo, en el código siguiente, quiero Línea 2 para volver 11 en lugar de 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 

La implementación diccionario actual utiliza object.Equals() y object.GetHashCode() en las teclas; pero estoy buscando un tipo diferente de diccionario que use la identidad del objeto como clave (en lugar del valor del objeto). ¿Existe tal diccionario en .NET o tengo que implementarlo desde cero?

+2

"identidad de objeto" no es un término en .NET - de hecho, su lenguaje está invertido: 'a' y' b' son objetos diferentes (con diferentes direcciones de memoria) pero con el mismo * valor * - su descripción implica que realmente desea usar el valor de la clave, no su dirección de memoria. – Dai

Respuesta

21

No es necesario que construya su propio diccionario; necesita construir su propia implementación de IEqualityComparer<T> que usa identidad para hash e igualdad. No creo que existe tal cosa en el marco, pero es fácil de construir debido a 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 
    } 
} 

He restringido T ser un tipo de referencia de modo que usted va a terminar con objetos en el diccionario; si usaste esto para tipos de valores, podrías obtener algunos resultados extraños. (No sé de improviso cómo funcionaría; I sospechoso no lo haría.)

Con eso en su lugar, el resto es fácil. Por ejemplo:

Dictionary<string, int> identityDictionary = 
    new Dictionary<string, int>(new IdentityEqualityComparer<string>()); 
+0

Hace poco construí una construcción similar aunque utilicé [object.ReferenceEquals] (http://msdn.microsoft.com/en-us/library/system.object.referenceequals.aspx) dentro del método Equals porque quería verificar igualdad de referencia incluso si el operador '==' se ha sobrecargado (lo que puede llamar un método Equals anulado para el objeto). –

+2

@JamesShuttler: Incluso si '==' está sobrecargado por * real * 'T' en el momento de la ejecución, la restricción aquí no obliga a' T' a ser algo que está sobrecargado, por lo que * * será referencia de igualdad. (Pruébalo con una cadena, por ejemplo ...) –

+0

buen punto :) –

4

utilizar su propio igualdad comparador

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); 
    } 
} 

Tenga en cuenta que GetHashCode Se pueden anular, pero la fundamental de verificación se hace con Equals.

+1

Creo que te refieres a redefinido, pero aún así, sería mejor usar 'RuntimeHelpers.GetHashCode' para mayor eficiencia. (¿Alguna razón para usar la implementación explícita para la mitad pero no para la otra, por cierto?) –

+0

Sí, por la causa, lo cambié. –

+1

Usé la implementación de Microsoft de 'MS.Internal.ComponentModel.ReferenceEqualityComparer'. También me pregunto por qué mezclaron la implementación implícita/explícita. Cambié mi código para ser más consistente. –

10

Por supuesto las otras respuestas son del todo correcto, pero me escribió mi propia versión para satisfacer mis necesidades:

/// <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); 
    } 
} 

Diseño razón de ser:

  • La clase es sealed.

    Si la clase no está diseñada para ser extendida, voy a evitar todo ese gasto sellándola.
    - Eric Lippert

    Conozco a muchas personas (incluyéndome a mí) que creen que las clases deberían estar selladas por defecto.
    - Jon Skeet

  • Hay un Instance static read-only property para exponer una única instancia de esta clase.
  • Utiliza Object.ReferenceEquals() en lugar de == porque ReferenceEquals es más explícito.
  • Utiliza RuntimeHelpers.GetHashCode() porque no quiero utilizar el posiblemente GetHashCode reemplazado del objeto, que puede no coincidir con el comportamiento de ReferenceEquals. Esto también evita una verificación nula.
  • Tiene documentación.
+0

Normalmente me parece más un 'crimen' tener una clase pública que una clase no sellada. Muy pocas clases terminan siendo parte de la API pública deseada: y a menos que deba ser pública, ¡mantenla interna (para el proyecto)! – user2864740

Cuestiones relacionadas