Como usar o IEqualityComparer

97

Tenho alguns sinos em meu banco de dados com o mesmo número. Eu quero obter todos eles sem duplicação. Criei uma classe de comparação para fazer este trabalho, mas a execução da função causa um grande atraso da função sem distinção, de 0,6 seg a 3,2 seg!

Estou fazendo certo ou preciso usar outro 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;
    }
}
Akrem
fonte
16
Você pode querer dar uma olhada em Diretrizes e regras para GetHashCode
Conrad Frix
Este blog explica como usar o IEqualityComparer perfeitamente: blog.alex-turok.com/2013/03/c-linq-and-iequalitycomparer.html
Jeremy Ray Brown

Respostas:

173

Sua GetHashCodeimplementação sempre retorna o mesmo valor. Distinctdepende de uma boa função hash para funcionar de forma eficiente, pois cria internamente uma tabela hash .

Ao implementar interfaces de classes, é importante ler a documentação para saber qual contrato você deve implementar. 1

Em seu código, a solução é encaminhá GetHashCode- Class_reglement.Numf.GetHashCodelo e implementá-lo apropriadamente lá.

Além disso, seu Equalsmétodo está cheio de códigos desnecessários. Ele poderia ser reescrito da seguinte forma (mesma semântica, ¼ do código, mais legível):

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

Por último, a ToListchamada é desnecessária e demorada: AddRangeaceita qualquer, IEnumerableportanto a conversão para a Listnão é necessária. tambémAsEnumerable é redundante aqui, pois o processamento do resultado causará isso de qualquer maneira.AddRange


1 Escrever código sem saber o que ele realmente faz é chamado de programação de culto à carga . É uma prática surpreendentemente difundida. Basicamente, não funciona.

Konrad Rudolph
fonte
19
Seu Equals falha quando x ou y são nulos.
dzendras
4
@dzendras O mesmo para GetHashCode. No entanto, observe que a documentação doIEqualityComparer<T> não especifica o que fazer com os nullargumentos - mas os exemplos fornecidos no artigo também não tratam null.
Konrad Rudolph
49
Uau. A abominação é desnecessariamente dura. Estamos aqui para ajudar uns aos outros, não para insultar. Acho que faz algumas pessoas rir, mas recomendo removê-lo.
Jess
4
+1 por me fazer ler sobre "programação de culto à carga" no wiki e depois mudar meu slogan do skype para "// A magia profunda começa aqui ... seguida por alguma magia pesada".
Alex
4
@NeilBenn Você confunde conselho franco com grosseria. Visto que OP aceitou a resposta (e, devo observar, em uma versão muito mais severa!), Eles não pareceram cometer o mesmo erro. Não sei por que você acha que dar conselhos é rude, mas se engana quando diz que “o cara não precisa de sermão”. Discordo totalmente: a palestra era necessária e foi levada a sério. O código, conforme escrito, era ruim e baseado em práticas de trabalho inadequadas. Não apontar isso seria um desserviço e nada útil, já que o OP não poderia melhorar seu funcionamento.
Konrad Rudolph
47

Experimente 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();
    }
}

Exemplo de seu uso seria

collection = collection
    .Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
    .ToList(); 
Suneelsarraf
fonte
19
GetHashCodeprecisa usar a expressão também: return _expr.Invoke(obj).GetHashCode();Veja esta postagem para um uso dela.
orad
3

Apenas código, com implementação GetHashCodee NULLvalidação:

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;
    }
}

Exemplo: lista de Class_reglement distinta por Numf

List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
Shahar Shokrani
fonte
2

A inclusão de sua classe de comparação (ou mais especificamente a AsEnumerablechamada que você precisava usar para fazê-la funcionar) significava que a lógica de classificação passou de ser baseada no servidor de banco de dados para estar no cliente de banco de dados (seu aplicativo). Isso significa que seu cliente agora precisa recuperar e depois processar um número maior de registros, o que sempre será menos eficiente do que realizar a consulta no banco de dados onde os índices apropriados podem ser usados.

Você deve tentar desenvolver uma cláusula where que satisfaça seus requisitos, consulte Usando um IEqualityComparer com uma cláusula LINQ to Entities Except para obter mais detalhes.

Justin
fonte
2

Se você deseja uma solução genérica sem boxe:

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)
user764754
fonte
0

IEquatable<T> pode ser uma maneira muito mais fácil de fazer isso com estruturas modernas.

Você obtém uma bool Equals(T other)função simples e agradável e não há confusão em lançar ou criar uma classe 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 
    }
}

Observe que você TEM que implementar GetHashCodese usar isso em um dicionário ou algo semelhante Distinct.

PS. Não acho que nenhum método Equals personalizado funcione com estrutura de entidade diretamente no lado do banco de dados (acho que você sabe disso porque faz AsEnumerable), mas esse é um método muito mais simples de fazer um Equals simples para o caso geral.

Se as coisas não parecem estar funcionando (como erros de chave duplicados ao fazer ToDictionary), coloque um ponto de interrupção dentro de Equals para ter certeza de que está sendo atingido e certifique-se de ter GetHashCodedefinido (com a palavra-chave override).

Simon_Weaver
fonte
1
Você ainda precisa verificar se há null
disklosr
Nunca me deparei com isso, mas vou me lembrar de fazer isso na próxima vez. Você tem um nulo em um List <T> ou algo parecido?
Simon_Weaver
1
De acordo com o .Equals()método, você parece ter comparado other.Hometowna si mesmo, em vez dethis.Hometown
Jake Stokes
Opa.
Erro de