Pregunta

Tengo un objeto de valor inmutable, IPathwayModule, cuyo valor está definido por:

  • (int) del bloque;
  • (Entidad) Módulo, identificado por ModuleId (cadena);
  • (enum) Estado; y
  • (entidad) Clase, identificado por (string) ClassId -. Que puede ser nulo

Aquí está mi aplicación IEqualityComparer actual, que parece funcionar en unas pocas pruebas unitarias. Sin embargo, no creo que entiendo lo que estoy haciendo bastante bien como para saber si lo estoy haciendo bien. Una aplicación anterior a veces fallar en repetidas pruebas de funcionamiento.

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        int hx = GetHashCode(x);
        int hy = GetHashCode(y);
        return hx == hy;
    }

    public int GetHashCode(IPathwayModule obj)
    {
        int h;
        if (obj.Class != null)
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + obj.Class.ClassId.GetHashCode();
        }
        else
        {
            h = obj.Block.GetHashCode() + obj.Module.ModuleId.GetHashCode() + obj.Status.GetHashCode() + "NOCLASS".GetHashCode();
        }
        return h;
    }
}

IPathwayModule es definitivamente inmutable y diferentes instancias con los mismos valores debe ser igual y producir el mismo HashCode ya que se utilizan como elementos dentro de HashSets.

supongo que mis preguntas son:

  • ¿Estoy usando la interfaz correctamente en este caso?
  • ¿Hay casos en los que no pueda ver el comportamiento deseado?
  • ¿Hay alguna forma de mejorar la robustez, rendimiento?
  • ¿Hay buenas prácticas que no estoy siguiendo?
¿Fue útil?

Solución 7

Gracias a todos los que respondieron. He agregado la retroalimentación de todos los que respondieron y mi mejorado IEqualityComparer ahora queda como:

private class StandardPathwayModuleComparer : IEqualityComparer<IPathwayModule>
{
    public bool Equals(IPathwayModule x, IPathwayModule y)
    {
        if (x == y) return true;
        if (x == null || y == null) return false;

        if ((x.Class == null) ^ (y.Class == null)) return false;

        if (x.Class == null) //and implicitly y.Class == null
        {
            return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId);
        }
        return x.Block.Equals(y.Block) && x.Status.Equals(y.Status) && x.Module.ModuleId.Equals(y.Module.ModuleId) && x.Class.ClassId.Equals(y.Class.ClassId);
    }
    public int GetHashCode(IPathwayModule obj)
    {
        unchecked {
            int h = obj.Block ^ obj.Module.ModuleId.GetHashCode() ^ (int) obj.Status;
            if (obj.Class != null)
            {
               h ^= obj.Class.ClassId.GetHashCode();
            }
            return h;
        }
    }
}

Otros consejos

No hacer los Iguales en términos de resultados de la función hash de que sea demasiado frágil. Más bien hacer una comparación de valor de campo para cada uno de los campos. Algo así como:

return x != null && y != null && x.Name.Equals(y.Name) && x.Type.Equals(y.Type) ...

Además, los resultados de las funciones hash no son muy susceptibles a la adición. Trate de usar el operador ^ lugar.

return obj.Name.GetHashCode() ^ obj.Type.GetHashCode() ...

No es necesario el cheque nulo en GetHashCode. Si ese valor es nulo, tienes problemas más grandes, inútil tratar de recuperarse de algo sobre lo que no tiene ningún control ...

El único gran problema es la implementación de Iguales. códigos hash no son únicos, se puede obtener el mismo código hash para objetos que son diferentes. Debe comparar cada campo de IPathwayModule individualmente.

GetHashCode () se puede mejorar un poco. No es necesario llamar GetHashCode () en un int. El int sí es un buen código hash. Lo mismo para los valores de enumeración. Su GetHashCode podría entonces implementa de la siguiente:

public int GetHashCode(IPathwayModule obj)
{
    unchecked {
        int h = obj.Block + obj.Module.ModeleId.GetHashCode() + (int) obj.Status;
        if (obj.class != null)
           h += obj.Class.ClassId.GetHashCode();
        return h;
    }
}

es necesario el bloque 'sin marcar' porque puede haber desbordamientos en las operaciones aritméticas.

No use GetHashCode () como la principal forma de objetos de comparación. Comparar el campo de TI se refiere.

Puede haber varios objetos con el mismo código hash (esto se llama 'código de colisiones hash').

Además, tenga cuidado al sumar múltiples valores enteros, ya que se puede producir fácilmente un OverflowException. Utilice 'o exclusiva' (^) para combinar hashcodes o una envoltura de código en el bloque 'sin control'.

Debe implementar mejores versiones de los Iguales y GetHashCode.

Por ejemplo, el código hash de enumeraciones no es más que su valor numérico.

En otras palabras, con estas dos enumeraciones:

public enum A { x, y, z }
public enum B { k, l, m }

A continuación, con su aplicación, el siguiente tipo de valor:

public struct AB {
    public A;
    public B;
}

los dos valores siguientes se consideran iguales:

AB ab1 = new AB { A = A.x, B = B.m };
AB ab2 = new AB { A = A.z, B = B.k };

Estoy asumiendo que no desea eso.

También, pasando los tipos de valor como interfaces se casilla ellos, esto podría tener problemas de rendimiento, aunque probablemente no mucho. Se podría considerar la posibilidad de la aplicación IEqualityComparer tomar directamente los tipos de valor.

  1. Suponiendo que dos objetos son iguales debido a que su código hash es igual está mal. Es necesario comparar todos los miembros de forma individual
  2. Es mejor utilizar proabably ^ + en lugar de combinar los códigos hash.

Si he entendido bien, desea escuchar algunos comentarios en su código. Acá mis observaciones:

  1. GetHashCode debe XOR'ed juntos, no se añade. XOR (^) da una mejor oportunidad de evitar colisiones
  2. Se trata de comparar hashcodes. Eso es bueno, pero sólo hacer esto si el objeto subyacente anula el GetHashCode. Si no es así, utilizar las propiedades y sus hashcodes y combinarlos.
  3. códigos hash son importantes, hacen una rápida posible comparar. Pero si códigos hash son iguales, el objeto todavía puede ser diferente. Esto no suele suceder. Sin embargo, usted necesita para comparar los campos de su objeto si códigos hash son iguales.
  4. Usted dice que sus tipos de valor son inmutables, sino que hacen referencia a objetos (.Class), que no son inmutables
  5. Siempre optimizar comparación mediante la adición de comparación de referencia como primera prueba. Referencias desiguales, los objetos son desiguales, entonces las estructuras son desiguales.

El punto 5 depende de si el desea que los objetos que se haga referencia en su tipo de valor de retorno no iguales cuando no la misma referencia.

EDIT: se compara muchas cuerdas. La comparación de cadenas se optimiza en C #. Usted puede, como otros sugirieron, un mejor uso == con ellos en su comparación. Para el GetHashCode, uso o ^ como se sugiere por otros también.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top