Cuando se agrega un objeto a la clase .NET System.Collections.Generic.Dictionary, el código hash de la clave se almacena internamente y se usa para comparaciones posteriores. Cuando el código hash cambia después de su inserción inicial en el diccionario, a menudo se vuelve "inaccesible" y puede sorprender a sus usuarios cuando una comprobación de existencia, incluso utilizando la misma referencia, devuelve falso (código de muestra a continuación).¿Se ha roto el diccionario o debería GetHashCode() solo base en miembros inmutables?
La documentación GetHashCode dice:
El método GetHashCode para un objeto siempre debe devolver el mismo código hash, siempre y cuando no hay ninguna modificación en el estado del objeto que determina el valor de retorno de del objeto es igual método.
Así, de acuerdo con los documentos GetHashCode
, el código hash puede cambiar cada vez que se cambia el estado equality -determinar, sin embargo, la aplicación Dictionary
no apoyar esto.
¿La implementación actual del diccionario .NET está rota porque ignora incorrectamente los permisos de hashcode? ¿Debería GetHashCode()
basarse solamente en miembros inmutables? O, ¿hay algo más para romper una posible falsa dicotomía?
class Hashable
{
public int PK { get; set; }
public override int GetHashCode()
{
if (PK != 0) return PK.GetHashCode();
return base.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as Hashable);
}
public virtual bool Equals(Hashable other)
{
if (other == null) return false;
else if (ReferenceEquals(this, other)) return true;
else if (PK != 0 && other.PK != 0) return Equals(PK, other.PK);
return false;
}
public override string ToString()
{
return string.Format("Hashable {0}", PK);
}
}
class Test
{
static void Main(string[] args)
{
var dict = new Dictionary<Hashable, bool>();
var h = new Hashable();
dict.Add(h, true);
h.PK = 42;
if (!dict.ContainsKey(h)) // returns false, despite same reference
dict.Add(h, false);
}
}
Si un diccionario admite llaves mutantes, tendría que engancharse en las teclas y de alguna manera ser notificado cada vez que cambie GetHashCode(). La implicación de hacer eso suena enorme, probablemente simplemente imposible. – Anonym
Siempre asumí que el diccionario se basaba en algo así como un identificador o puntero de objeto subyacente (que aún no estoy seguro de que tenga .NET), pero tiene razón: HashCode es una opción de implementación perfecta para un diccionario. –
De hecho, este diseño es * permitir * que use tipos más complejos como claves; solo necesitas diseñarlos adecuadamente. (Si usa un puntero de objeto, por ejemplo, podría terminar con duplicados en el diccionario debido a tener dos instancias de un tipo complejo como claves, aunque haya diseñado correctamente ese tipo complejo para representar, por ejemplo, una clave compuesta). –