2009-04-18 16 views
12

Después de leer todas las preguntas y respuestas en StackOverflow en relación primordial GetHashCode() escribí el siguiente método de extensión de primordial fácil y conveniente de GetHashCode():GetHashCode Método de extensión

public static class ObjectExtensions 
{ 
    private const int _seedPrimeNumber = 691; 
    private const int _fieldPrimeNumber = 397; 
    public static int GetHashCodeFromFields(this object obj, params object[] fields) { 
     unchecked { //unchecked to prevent throwing overflow exception 
      int hashCode = _seedPrimeNumber; 
      for (int i = 0; i < fields.Length; i++) 
       if (fields[i] != null) 
        hashCode *= _fieldPrimeNumber + fields[i].GetHashCode(); 
      return hashCode; 
     } 
    } 
} 

(que básicamente sólo se refactorizado el código que alguien publicó allí , porque me gusta mucho que se puede utilizar en general)

que utilizo como esto:

public override int GetHashCode() { 
     return this.GetHashCodeFromFields(field1, field2, field3); 
    } 

¿Ve algún problema con este código?

+1

El código se ve bien. Como mejora, puede verificar si los campos [i] no son nulos. –

+0

Solo una adición: no se recomienda agregar métodos de extensión al tipo de objeto. –

+0

Lo sé, pero ¿por qué? En realidad, no hemos cambiado la funcionalidad del objeto de ninguna manera. Tu sentido de la inteligencia solo estará desordenado. Y tampoco hay necesidad de hacer de esto un método de extensión, simplemente lo hace más conveniente. Todavía podría llamar al método estático que pasa en el objeto. –

Respuesta

2

que se parece a una forma sólida para hacerlo.

Mi única sugerencia es que si realmente está preocupado por el rendimiento con ella, es posible que desee agregar versiones genéricas para varios casos comunes (es decir, probablemente 1-4 args). De esa forma, para esos objetos (que probablemente sean pequeños, objetos compuestos de estilo clave), no tendrá la sobrecarga de construir la matriz para pasar al método, el bucle, cualquier boxeo de valores genéricos, etc. La sintaxis de la llamada será exactamente la misma, pero ejecutará un código ligeramente más optimizado para ese caso. Por supuesto, hice algunas pruebas de perfusión antes de decidir si vale la pena el compromiso de mantenimiento.

Algo como esto:

public static int GetHashCodeFromFields<T1,T2,T3,T4>(this object obj, T1 obj1, T2 obj2, T3 obj3, T4 obj4) { 
    int hashCode = _seedPrimeNumber; 
    if(obj1 != null) 
     hashCode *= _fieldPrimeNumber + obj1.GetHashCode(); 
    if(obj2 != null) 
     hashCode *= _fieldPrimeNumber + obj2.GetHashCode(); 
    if(obj3 != null) 
     hashCode *= _fieldPrimeNumber + obj3.GetHashCode(); 
    if(obj4 != null) 
     hashCode *= _fieldPrimeNumber + obj4.GetHashCode(); 
    return hashCode; 
} 
+0

Gracias Jonathan, creo que esto resolvería todos los problemas de rendimiento que usted y otros han señalado. También creo que la solución de generación de código fue genial, pero me gusta más. –

1

Me parece bastante bueno, solo tengo un problema: es una lástima que tenga que usar un object[] para pasar los valores ya que esto encasillará cualquier tipo de valor que envíe a la función. No obstante, no creo que tengas muchas opciones, a menos que vayas por la ruta de crear algunas sobrecargas genéricas como han sugerido otros.

+0

IIRC una llamada de función virtual en un tipo de valor encapsulará el valor. – leppie

+1

solo si el tipo NO proporciona una implementación para el método – ShuggyCoUk

0

En principio general, debe extender su unchecked tan estrechamente como sea razonablemente posible, aunque aquí no importa mucho. Aparte de eso, se ve bien.

0
public override int GetHashCode() { 
    return this.GetHashCodeFromFields(field1, field2, field3, this); 
} 

(sí, soy muy pedante, pero este es el único problema que veo)

+0

¿No causaría esto un ciclo infinito? –

+0

seguro, ni siquiera sé cómo arreglar ese – dfa

+1

si está tratando de proteger su función genérica contra un desarrollador estúpido, supongo que podría verificar los campos [i]! = Obj, pero este es un caso en el que Simplemente dejé que se bloquee y se queme si el usuario es tan estúpido ... –

0

más óptima:

  1. Crear un generador de código que utiliza la reflexión para mirar a través de sus campos de objeto de negocio y crea una nueva clase parcial que anula GetHashCode() (e Igual()).
  2. Ejecute el generador de código cuando su programa se inicie en modo de depuración, y si el código ha cambiado, salga con un mensaje al desarrollador para que recompile.

Las ventajas de esto son:

  • Usando reflexión sabes qué campos son los tipos de valor o no, y por lo tanto si necesitan cheques nulos.
  • No hay gastos generales, no hay llamadas a funciones adicionales, no hay construcción de lista, etc. Esto es importante si está realizando muchas búsquedas de diccionario.
  • Las implementaciones largas (en clases con muchos campos) están ocultas en clases parciales, lejos de su código comercial importante.

Desventajas:

  • Overkill si no hacer un montón de búsquedas de diccionario/llamadas a GetHashCode().
0

Debo señalar que casi nunca debe hacer mientras se implementa la asignación GetHashCode (aquí está someusefulblog puestos sobre él).

La forma en que funciona params (generando una nueva matriz sobre la marcha) significa que esto no es realmente una buena solución general. Sería mejor usar una llamada de método por campo y mantener el estado de hash como una variable que se les pasa (esto facilita el uso de mejores funciones de hashing y avalanchas también).

+0

Gracias por los enlaces de Shuggy. Muy interesante. No sabía que las implementaciones GetHashCode de WPF Pen, Font, Color y Brush eran tan 'pesadas'. Los estoy usando mucho, pero afortunadamente no como claves en mis diccionarios. –

+0

Pueden haber solucionado esto desde esa publicación ... Tengo algunas funciones ingeniosas para encadenar hashes juntos (usando Jenkins uno a la vez hash) Trataré de abrirlo más tarde – ShuggyCoUk

+0

No hay nada de malo en 'GetHashCode () 'función que asigna memoria * la primera vez que se llama *, y no hay nada mal con una que lleva algún tiempo para calcular. Sin embargo, los objetos cuyas funciones hash que asignan memoria o tardan tiempo en computar, deben almacenar en caché sus valores hash para que las solicitudes repetidas se completen rápidamente. – supercat

3

escribí algunas cosas un poco de tiempo atrás que pueda resolver su problema ... (Y, de hecho, es probable que podría mejorarse para incluir la semilla que usted tiene ...)

De todos modos, el proyecto es llamado Esencia (http://essence.codeplex.com/), y utiliza las bibliotecas para generar System.Linq.Expression (basado en atributos) representaciones estándar de iguales/GetHashCode/CompareTo/ToString, además de ser capaz de crear clases IEqualityComparer y IComparer basado en una lista de argumentos . (También tengo algunas ideas adicionales, pero me gustaría obtener algunos comentarios de la comunidad antes de continuar mucho más).

(Lo que esto significa es que es casi tan rápido como estar escrito a mano, el principal donde no lo es la CompareTo(); causar los Linq.Expressions no tiene el concepto de una variable en la liberación 3.5 - lo que tiene que llamar CompareTo() en el objeto que subyace dos veces cuando usted no recibe un partido Uso de las extensiones de DLR. a Linq.Expressions resuelve esto. Supongo que podría haber usado el emit il, pero no estaba tan inspirado en ese momento.)

Es una idea bastante simple, pero no lo había visto antes.

Ahora, el tema es que perdí interés en pulirlo (lo que hubiera incluido escribir un artículo para codeproject, documentar parte del código, etc.), pero podría persuadirse para que lo haga si se siente sería algo de interés.

(El sitio codeplex no tiene un paquete descargable, solo ve a la fuente y toma eso - oh, está escrito en f # (aunque todo el código de prueba está en C#) ya que eso era lo que a mí me interesaba . aprendizaje)

de todos modos, aquí está son C# ejemplo de la prueba en el proyecto:

// -------------------------------------------------------------------- 
    // USING THE ESSENCE LIBRARY: 
    // -------------------------------------------------------------------- 
    [EssenceClass(UseIn = EssenceFunctions.All)] 
    public class TestEssence : IEquatable<TestEssence>, IComparable<TestEssence> 
    { 
     [Essence(Order=0] public int MyInt   { get; set; } 
     [Essence(Order=1] public string MyString  { get; set; } 
     [Essence(Order=2] public DateTime MyDateTime { get; set; } 

     public override int GetHashCode()        { return Essence<TestEssence>.GetHashCodeStatic(this); } 
    ... 
    } 

    // -------------------------------------------------------------------- 
    // EQUIVALENT HAND WRITTEN CODE: 
    // -------------------------------------------------------------------- 
    public class TestManual 
    { 
     public int MyInt; 
     public string MyString; 
     public DateTime MyDateTime; 

     public override int GetHashCode() 
     { 
      var x = MyInt.GetHashCode(); 
      x *= Essence<TestEssence>.HashCodeMultiplier; 
      x ^= (MyString == null) ? 0 : MyString.GetHashCode(); 
      x *= Essence<TestEssence>.HashCodeMultiplier; 
      x ^= MyDateTime.GetHashCode(); 
      return x; 
     } 
    ... 
    } 

de todos modos, el proyecto, si alguien piensa que vale la pena, es necesario pulir, pero las ideas están ahí ...

+0

Agradable, pero definitivamente mucho más lento que impl escrito a mano. –

+0

No, en realidad no. ¡Visita http://essence.codeplex.com/ y dale una burla! –

0

Aparte de los problemas derivados de la aplicación params object[] fields, creo que no usar el tipo de información puede ser un problema de rendimiento en algunas situaciones también. Supongamos dos clases A, B tienen el mismo tipo y número de campos e implementar la misma interfaz I. Ahora bien, si se pone A y B objetos a un Dictionary<I, anything> objetos con los mismos campos y diferentes tipos va a terminar en el mismo cubo.Probablemente insertaría alguna declaración como hashCode ^= GetType().GetHashCode();

La respuesta aceptada de Jonathan Rupp trata con params array pero no trata con el boxeo de tipos de valores. Por lo tanto, si el rendimiento es muy importante, probablemente declararía GetHashCodeFromFields con parámetros no objeto pero int, y no enviaría los campos en sí, sino los códigos hash de los campos. es decir

public override int GetHashCode() 
{ 
    return this.GetHashCodeFromFields(field1.GetHashCode(), field2.GetHashCode()); 
} 
0

Un problema que podría surgir es cuando la multiplicación golpea 0, hashCode final es siempre 0, como acabo experimentado con un objeto con una gran cantidad de propiedades, en el siguiente código:

hashCode *= _fieldPrimeNumber + fields[i].GetHashCode(); 

yo sugeriría:

hashCode = hashCode * _fieldPrimeNumber + fields[i].GetHashCode(); 

O algo similar con XOR como this:

hashCode = hashCode * _fieldPrimeNumber^fields[i].GetHashCode();