property method generic delegate array c# arrays generics hashcode

c# - method - GetHashCode anulación de objeto que contiene matriz genérica



generic restrictions c# (9)

Tengo una clase que contiene las siguientes dos propiedades:

public int Id { get; private set; } public T[] Values { get; private set; }

Lo he convertido en IEquatable<T> y object.Equals el object.Equals como este:

public override bool Equals(object obj) { return Equals(obj as SimpleTableRow<T>); } public bool Equals(SimpleTableRow<T> other) { // Check for null if(ReferenceEquals(other, null)) return false; // Check for same reference if(ReferenceEquals(this, other)) return true; // Check for same Id and same Values return Id == other.Id && Values.SequenceEqual(other.Values); }

Al reemplazar el object.Equals , también debo anular GetHashCode por supuesto. Pero, ¿qué código debería implementar? ¿Cómo creo un código hash fuera de una matriz genérica? ¿Y cómo lo combino con el número entero de Id ?

public override int GetHashCode() { return // What? }


¿Qué tal algo así como:

public override int GetHashCode() { int hash = Id; if (Values != null) { hash = (hash * 17) + Values.Length; foreach (T t in Values) { hash *= 17; if (t != null) hash = hash + t.GetHashCode(); } } return hash; }

Esto debería ser compatible con SequenceEqual , en lugar de hacer una comparación de referencia en la matriz.


Dado que hashCode es una clave para almacenar el objeto (como una tabla hash), solo usaría Id.GetHashCode ()


Debido a los problemas planteados en este hilo, estoy publicando otra respuesta que muestra lo que sucede si te equivocas ... principalmente, que no puedes usar el GetHashCode() la matriz; el comportamiento correcto es que no se imprimen advertencias cuando lo ejecuta ... cambie los comentarios para solucionarlo:

using System; using System.Collections.Generic; using System.Linq; static class Program { static void Main() { // first and second are logically equivalent SimpleTableRow<int> first = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6), second = new SimpleTableRow<int>(1, 2, 3, 4, 5, 6); if (first.Equals(second) && first.GetHashCode() != second.GetHashCode()) { // proven Equals, but GetHashCode() disagrees Console.WriteLine("We have a problem"); } HashSet<SimpleTableRow<int>> set = new HashSet<SimpleTableRow<int>>(); set.Add(first); set.Add(second); // which confuses anything that uses hash algorithms if (set.Count != 1) Console.WriteLine("Yup, very bad indeed"); } } class SimpleTableRow<T> : IEquatable<SimpleTableRow<T>> { public SimpleTableRow(int id, params T[] values) { this.Id = id; this.Values = values; } public int Id { get; private set; } public T[] Values { get; private set; } public override int GetHashCode() // wrong { return Id.GetHashCode() ^ Values.GetHashCode(); } /* public override int GetHashCode() // right { int hash = Id; if (Values != null) { hash = (hash * 17) + Values.Length; foreach (T t in Values) { hash *= 17; if (t != null) hash = hash + t.GetHashCode(); } } return hash; } */ public override bool Equals(object obj) { return Equals(obj as SimpleTableRow<T>); } public bool Equals(SimpleTableRow<T> other) { // Check for null if (ReferenceEquals(other, null)) return false; // Check for same reference if (ReferenceEquals(this, other)) return true; // Check for same Id and same Values return Id == other.Id && Values.SequenceEqual(other.Values); } }


FWIW, es muy peligroso usar los contenidos de los valores en tu código hash. Solo debe hacer esto si puede garantizar que nunca cambiará. Sin embargo, dado que está expuesto, no creo que se garantice que sea posible. El hashcode de un objeto nunca debería cambiar. De lo contrario, pierde su valor como clave en una Hashtable o Dictionary. ¡Considere el error difícil de encontrar de usar un objeto como clave en una Hashtable, sus cambios en el código hash debido a una influencia externa y ya no puede encontrarlo en la Hashtable!


Lo haría de esta manera:

long result = Id.GetHashCode(); foreach(T val in Values) result ^= val.GetHashCode(); return result;


Sé que este hilo es bastante antiguo, pero escribí este método para permitirme calcular hashcodes de múltiples objetos. Ha sido muy útil para este mismo caso. No es perfecto, pero cumple mis necesidades y probablemente también las tuyas.

Realmente no puedo tomar ningún crédito por eso. Obtuve el concepto de algunas de las implementaciones .net gethashcode. Estoy usando 419 (después de todo, es mi primer preferido grande), pero puedes elegir cualquier primo razonable (no demasiado pequeño ... no demasiado grande).

Entonces, así es como obtengo mis hashcodes:

using System.Collections.Generic; using System.Linq; public static class HashCodeCalculator { public static int CalculateHashCode(params object[] args) { return args.CalculateHashCode(); } public static int CalculateHashCode(this IEnumerable<object> args) { if (args == null) return new object().GetHashCode(); unchecked { return args.Aggregate(0, (current, next) => (current*419) ^ (next ?? new object()).GetHashCode()); } } }


Siempre que Id and Values ​​nunca cambie, y Values ​​no es nulo ...

public override int GetHashCode() { return Id ^ Values.GetHashCode(); }

Tenga en cuenta que su clase no es inmutable, ya que cualquiera puede modificar el contenido de los valores porque es una matriz. Dado eso, no trataría de generar un hashcode usando sus contenidos.


Solo tuve que agregar otra respuesta porque no se mencionó una de las soluciones más obvias (y la más fácil de implementar), ¡sin incluir la colección en su cálculo de GetHashCode !

Lo principal que parecía haberse olvidado aquí es que la exclusividad del resultado de GetHashCode no es necesaria (o en muchos casos posible). Los objetos desiguales no tienen que devolver códigos hash desiguales, el único requisito es que los objetos iguales devuelvan los mismos códigos hash. Entonces, según esa definición, la siguiente implementación de GetHashCode es correcta para todos los objetos (suponiendo que haya una implementación Equals correcta):

public override int GetHashCode() { return 42; }

Por supuesto, esto produciría el peor rendimiento posible en la búsqueda de hashtable, O (n) en lugar de O (1), pero sigue siendo funcionalmente correcto.

Con esto en mente, mi recomendación general al implementar GetHashCode para un objeto que tiene algún tipo de colección como uno o más de sus miembros es simplemente ignorarlos y calcular GetHashCode únicamente en base a los otros miembros escalares. Esto funcionaría bastante bien, excepto si coloca en una tabla hash una gran cantidad de objetos donde todos sus miembros escalares tienen valores idénticos, lo que da como resultado códigos hash idénticos.

Ignorar los miembros de la colección cuando se calcula el código hash también puede producir una mejora en el rendimiento, a pesar de la menor distribución de los valores del código hash. Recuerde que usar un código hash se supone que mejora el rendimiento en una tabla hash al no requerir llamar a Equals N veces, y en su lugar solo requerirá llamar a GetHashCode una vez y una búsqueda rápida en la tabla hash. Si cada objeto tiene una matriz interna con 10.000 elementos que todos participan en el cálculo del código hash, probablemente se perderán los beneficios obtenidos por la buena distribución. Sería mejor tener un código hash marginalmente menos distribuido si generarlo es considerablemente menos costoso.


public override int GetHashCode() { return Id.GetHashCode() ^ Values.GetHashCode(); }

Hay varios puntos buenos en los comentarios y otras respuestas. El OP debería considerar si los valores se usarían como parte de la "clave" si el objeto se usara como clave en un diccionario. Si es así, entonces deberían formar parte del código hash; de lo contrario, no.

Por otro lado, no estoy seguro de por qué el método GetHashCode debe reflejar SequenceEqual. Está destinado a calcular un índice en una tabla hash, no a ser el determinante completo de la igualdad. Si hay muchas colisiones de tabla hash utilizando el algoritmo anterior, y si difieren en la secuencia de los valores, entonces se debe elegir un algoritmo que tenga en cuenta la secuencia. Si la secuencia realmente no importa, ahorre tiempo y no la tenga en cuenta.