Cómo utilizar IEqualityComparer


97

Tengo algunas campanas en mi base de datos con el mismo número. Quiero obtenerlos todos sin duplicarlos. Creé una clase de comparación para hacer este trabajo, pero la ejecución de la función provoca un gran retraso de la función sin distinción, ¡de 0,6 segundos a 3,2 segundos!

¿Lo estoy haciendo bien o tengo que usar otro método?

reg.AddRange(
    (from a in this.dataContext.reglements
     join b in this.dataContext.Clients on a.Id_client equals b.Id
     where a.date_v <= datefin && a.date_v >= datedeb
     where a.Id_client == b.Id
     orderby a.date_v descending 
     select new Class_reglement
     {
         nom  = b.Nom,
         code = b.code,
         Numf = a.Numf,
     })
    .AsEnumerable()
    .Distinct(new Compare())
    .ToList());

class Compare : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x.Numf == y.Numf)
        {
            return true;
        }
        else { return false; }
    }
    public int GetHashCode(Class_reglement codeh)
    {
        return 0;
    }
}

16
Es posible que desee echar un vistazo a las Pautas y reglas para GetHashCode
Conrad Frix

Este blog explica cómo usar IEqualityComparer perfectamente: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Respuestas:


173

Su GetHashCodeimplementación siempre devuelve el mismo valor. Distinctse basa en una buena función hash para funcionar de manera eficiente porque crea internamente una tabla hash .

Al implementar interfaces de clases, es importante leer la documentación para saber qué contrato se supone que debe implementar. 1

En su código, la solución es reenviar GetHashCodea Class_reglement.Numf.GetHashCodee implementar de manera adecuada allí.

Aparte de eso, su Equalsmétodo está lleno de código innecesario. Se podría reescribir de la siguiente manera (misma semántica, ¼ del código, más legible):

public bool Equals(Class_reglement x, Class_reglement y)
{
    return x.Numf == y.Numf;
}

Por último, la ToListllamada es innecesaria y requiere mucho tiempo: AddRangeacepta cualquiera, IEnumerablepor lo que Listno es necesaria la conversión a a . TambiénAsEnumerable es redundante aquí, ya que procesar el resultado provocará esto de todos modos.AddRange


1 Escribir código sin saber lo que realmente hace se llama programación de culto de carga . Es una práctica sorprendentemente extendida. Básicamente no funciona.


19
Your Equals falla cuando x o y son nulos.
dzendras

4
@dzendras Lo mismo para GetHashCode. Sin embargo, tenga en cuenta que la documentación deIEqualityComparer<T> no especifica qué hacer con los nullargumentos, pero los ejemplos proporcionados en el artículo tampoco lo admiten null.
Konrad Rudolph

49
Guau. La abominación es innecesariamente dura. Estamos aquí para ayudarnos unos a otros, no para insultarnos. Supongo que hace reír a algunas personas, pero recomendaría eliminarlo.
Jess

4
+1 por hacerme leer sobre "programación de culto de carga" en wiki y luego cambiar mi línea de etiqueta de skype a "// La magia profunda comienza aquí ... seguida de algo de magia pesada".
Alex

4
@NeilBenn Confundes un consejo franco con una mala educación. Dado que OP aceptó la respuesta (y, debo señalar, ¡en una versión mucho más severa!), No parecieron cometer el mismo error. No estoy seguro de por qué piensas que dar consejos es de mala educación, pero te equivocas cuando dices que "el tipo no necesita un sermón". Estoy totalmente en desacuerdo: la conferencia era necesaria y se tomó en serio. El código, tal como estaba escrito, era incorrecto y se basaba en malas prácticas laborales. No señalar esto sería un flaco favor y no sería de ninguna ayuda, ya que entonces el OP no podría mejorar su funcionamiento.
Konrad Rudolph

47

Prueba este código:

public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
    private Func<T, object> _expr { get; set; }
    public GenericCompare(Func<T, object> expr)
    {
        this._expr = expr;
    }
    public bool Equals(T x, T y)
    {
        var first = _expr.Invoke(x);
        var sec = _expr.Invoke(y);
        if (first != null && first.Equals(sec))
            return true;
        else
            return false;
    }
    public int GetHashCode(T obj)
    {
        return obj.GetHashCode();
    }
}

Ejemplo de su uso sería

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 

19
GetHashCodetambién necesita usar la expresión: return _expr.Invoke(obj).GetHashCode();consulte esta publicación para conocer su uso.
orad

3

Solo código, con implementación GetHashCodey NULLvalidación:

public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
    public bool Equals(Class_reglement x, Class_reglement y)
    {
        if (x is null || y is null))
            return false;

        return x.Numf == y.Numf;
    }

    public int GetHashCode(Class_reglement product)
    {
        //Check whether the object is null 
        if (product is null) return 0;

        //Get hash code for the Numf field if it is not null. 
        int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();

        return hashNumf;
    }
}

Ejemplo: lista de Class_reglement distinta por Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());

2

La inclusión de su clase de comparación (o más específicamente la AsEnumerablellamada que necesitaba usar para que funcione) significó que la lógica de clasificación pasó de estar basada en el servidor de la base de datos a estar en el cliente de la base de datos (su aplicación). Esto significa que su cliente ahora necesita recuperar y luego procesar una mayor cantidad de registros, lo que siempre será menos eficiente que realizar la búsqueda en la base de datos donde se pueden usar los índices apropiados.

En su lugar, debería intentar desarrollar una cláusula where que satisfaga sus requisitos; consulte Uso de IEqualityComparer con una cláusula LINQ to Entities Except para obtener más detalles.


2

Si desea una solución genérica sin boxeo:

public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    private readonly Func<T, TKey> _keyGetter;

    public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
    {
        _keyGetter = keyGetter;
    }

    public bool Equals(T x, T y)
    {
        return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
    }

    public int GetHashCode(T obj)
    {
        TKey key = _keyGetter(obj);

        return key == null ? 0 : key.GetHashCode();
    }
}

public static class KeyBasedEqualityComparer<T>
{
    public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
    {
        return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
    }
}

uso:

KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf)

0

IEquatable<T> puede ser una forma mucho más fácil de hacer esto con los marcos modernos.

Obtienes una bool Equals(T other)función simple y agradable y no hay que perder el tiempo con el casting o la creación de una clase separada.

public class Person : IEquatable<Person>
{
    public Person(string name, string hometown)
    {
        this.Name = name;
        this.Hometown = hometown;
    }

    public string Name { get; set; }
    public string Hometown { get; set; }

    // can't get much simpler than this!
    public bool Equals(Person other)
    {
        return this.Name == other.Name && this.Hometown == other.Hometown;
    }

    public override int GetHashCode()
    {
        return Name.GetHashCode();  // see other links for hashcode guidance 
    }
}

Tenga en cuenta que TIENE que implementarlo GetHashCodesi usa esto en un diccionario o con algo como Distinct.

PD. No creo que ningún método Equals personalizado funcione con el marco de la entidad directamente en el lado de la base de datos (creo que lo sabe porque hace AsEnumerable) pero este es un método mucho más simple para hacer Equals simple para el caso general.

Si las cosas no parecen estar funcionando (como errores de clave duplicada al hacer ToDictionary), coloque un punto de interrupción dentro de Equals para asegurarse de que se haya alcanzado y asegúrese de haber GetHashCodedefinido (con la palabra clave override).


1
Aún necesita verificar si hay nulo
disklosr

Nunca me encontré con eso, pero recordaré hacerlo la próxima vez. ¿Tiene un nulo en una List <T> o algo así?
Simon_Weaver

1
Según el .Equals()método, parece haberse comparado other.Hometownconsigo mismo, en lugar dethis.Hometown
Jake Stokes

¡Ups!
Error
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.