Pergunta

Várias funções Linq. IEqualityComparer<T>. Existe uma classe de invólucro conveniente que adapta um delegate(T,T)=>bool implementar IEqualityComparer<T>? É fácil escrever um (se você ignorar problemas na definição de um código de hashs correto), mas eu gostaria de saber se existe uma solução pronta para uso.

Especificamente, quero definir operações em DictionaryS, usando apenas as chaves para definir a associação (mantendo os valores de acordo com regras diferentes).

Foi útil?

Solução

Normalmente, eu resolveria isso comentar @sam sobre a resposta (eu fiz algumas edições na postagem original para limpá -la um pouco sem alterar o comportamento.)

O seguinte é o meu riff de @Resposta de Sam, com uma correção crítica [imnsho] para a política de hash padrão:-

class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => 0 ) // NB Cannot assume anything about how e.g., t.GetHashCode() interacts with the comparer's behavior
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

Outras dicas

Sobre a importância de GetHashCode

Outros já comentaram o fato de que qualquer costume IEqualityComparer<T> implementação deve realmente incluir um GetHashCode método; Mas ninguém se incomodou em explicar Por quê em qualquer detalhe.

Aqui está o porquê. Sua pergunta menciona especificamente os métodos de extensão LINQ; por pouco tudo Destes, depende de códigos de hash para funcionar corretamente, porque utilizam tabelas de hash internamente para obter eficiência.

Leva Distinct, por exemplo. Considere as implicações deste método de extensão se tudo o que utilizou foram um Equals método. Como você determina se um item já foi digitalizado em uma sequência se você só tem Equals? Você enumora em toda a coleção de valores que já examinou e verifica uma correspondência. Isso resultaria em Distinct Usando um pior caso O (n2) Algoritmo em vez de um (n) um!

Felizmente, esse não é o caso. Distinct não apenas usar Equals; ele usa GetHashCode também. Na verdade, absolutamente não trabalhar corretamente sem um IEqualityComparer<T> que fornece um adequado GetHashCode. Abaixo está um exemplo artificial que ilustra isso.

Diga que tenho o seguinte tipo:

class Value
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Value(string name, int number)
    {
        Name = name;
        Number = number;
    }

    public override string ToString()
    {
        return string.Format("{0}: {1}", Name, Number);
    }
}

Agora diga que tenho um List<Value> E eu quero encontrar todos os elementos com um nome distinto. Este é um caso de uso perfeito para Distinct usando um comparador de igualdade personalizado. Então, vamos usar o Comparer<T> classe de A resposta de Aku:

var comparer = new Comparer<Value>((x, y) => x.Name == y.Name);

Agora, se tivermos um monte de Value elementos com o mesmo Name propriedade, todos devem entrar em colapso em um valor retornado por Distinct, certo? Vamos ver...

var values = new List<Value>();

var random = new Random();
for (int i = 0; i < 10; ++i)
{
    values.Add("x", random.Next());
}

var distinct = values.Distinct(comparer);

foreach (Value x in distinct)
{
    Console.WriteLine(x);
}

Resultado:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

Hmm, isso não funcionou, funcionou?

A respeito GroupBy? Vamos tentar isso:

var grouped = values.GroupBy(x => x, comparer);

foreach (IGrouping<Value> g in grouped)
{
    Console.WriteLine("[KEY: '{0}']", g);
    foreach (Value x in g)
    {
        Console.WriteLine(x);
    }
}

Resultado:

[KEY = 'x: 1346013431']
x: 1346013431
[KEY = 'x: 1388845717']
x: 1388845717
[KEY = 'x: 1576754134']
x: 1576754134
[KEY = 'x: 1104067189']
x: 1104067189
[KEY = 'x: 1144789201']
x: 1144789201
[KEY = 'x: 1862076501']
x: 1862076501
[KEY = 'x: 1573781440']
x: 1573781440
[KEY = 'x: 646797592']
x: 646797592
[KEY = 'x: 655632802']
x: 655632802
[KEY = 'x: 1206819377']
x: 1206819377

Novamente: não funcionou.

Se você pensar sobre isso, faria sentido para Distinct para usar um HashSet<T> (ou equivalente) internamente e para GroupBy Para usar algo como um Dictionary<TKey, List<T>> internamente. Isso poderia explicar por que esses métodos não funcionam? Vamos tentar isso:

var uniqueValues = new HashSet<Value>(values, comparer);

foreach (Value x in uniqueValues)
{
    Console.WriteLine(x);
}

Resultado:

x: 1346013431
x: 1388845717
x: 1576754134
x: 1104067189
x: 1144789201
x: 1862076501
x: 1573781440
x: 646797592
x: 655632802
x: 1206819377

Sim ... começando a fazer sentido?

Esperançosamente desses exemplos, fica claro por que incluir um apropriado GetHashCode em qualquer IEqualityComparer<T> A implementação é tão importante.


Resposta original

Expandindo -se Resposta de Orip:

Existem algumas melhorias que podem ser feitas aqui.

  1. Primeiro, eu levaria um Func<T, TKey> ao invés de Func<T, object>; Isso impedirá o boxe das teclas do tipo de valor no real keyExtractor em si.
  2. Segundo, eu realmente adicionaria um where TKey : IEquatable<TKey> restrição; Isso impedirá o boxe no Equals ligar (object.Equals leva um object parâmetro; você precisa de um IEquatable<TKey> implementação para tomar um TKey parâmetro sem boxe). Claramente, isso pode representar uma restrição muito severa, para que você possa fazer uma classe base sem a restrição e uma classe derivada com ela.

Aqui está como pode parecer o código resultante:

public class KeyEqualityComparer<T, TKey> : IEqualityComparer<T>
{
    protected readonly Func<T, TKey> keyExtractor;

    public KeyEqualityComparer(Func<T, TKey> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public virtual bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

public class StrictKeyEqualityComparer<T, TKey> : KeyEqualityComparer<T, TKey>
    where TKey : IEquatable<TKey>
{
    public StrictKeyEqualityComparer(Func<T, TKey> keyExtractor)
        : base(keyExtractor)
    { }

    public override bool Equals(T x, T y)
    {
        // This will use the overload that accepts a TKey parameter
        // instead of an object parameter.
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }
}

Quando você deseja personalizar a verificação da igualdade, 99% do tempo está interessado em definir as chaves a se comparar, não pela própria comparação.

Esta pode ser uma solução elegante (conceito do Python's Método de classificação da lista).

Uso:

var foo = new List<string> { "abc", "de", "DE" };

// case-insensitive distinct
var distinct = foo.Distinct(new KeyEqualityComparer<string>( x => x.ToLower() ) );

o KeyEqualityComparer classe:

public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
    private readonly Func<T, object> keyExtractor;

    public KeyEqualityComparer(Func<T,object> keyExtractor)
    {
        this.keyExtractor = keyExtractor;
    }

    public bool Equals(T x, T y)
    {
        return this.keyExtractor(x).Equals(this.keyExtractor(y));
    }

    public int GetHashCode(T obj)
    {
        return this.keyExtractor(obj).GetHashCode();
    }
}

Receio que não haja tal invólucro fora da caixa. No entanto, não é difícil criar um:

class Comparer<T>: IEqualityComparer<T>
{
    private readonly Func<T, T, bool> _comparer;

    public Comparer(Func<T, T, bool> comparer)
    {
        if (comparer == null)
            throw new ArgumentNullException("comparer");

        _comparer = comparer;
    }

    public bool Equals(T x, T y)
    {
        return _comparer(x, y);
    }

    public int GetHashCode(T obj)
    {
        return obj.ToString().ToLower().GetHashCode();
    }
}

...

Func<int, int, bool> f = (x, y) => x == y;
var comparer = new Comparer<int>(f);
Console.WriteLine(comparer.Equals(1, 1));
Console.WriteLine(comparer.Equals(1, 2));

O mesmo que a resposta de Dan Tao, mas com algumas melhorias:

  1. Confia em EqualityComparer<>.Default Para fazer a comparação real para evitar o boxe para tipos de valor (structs) que implementou IEquatable<>.

  2. Desde EqualityComparer<>.Default Usado não explodiu em null.Equals(something).

  3. Forneceu um invólucro estático ao redor IEqualityComparer<> que terá um método estático para criar a instância do comparador - facilita a chamada. Comparar

    Equality<Person>.CreateComparer(p => p.ID);
    

    com

    new EqualityComparer<Person, int>(p => p.ID);
    
  4. Adicionou uma sobrecarga para especificar IEqualityComparer<> para a chave.

A classe:

public static class Equality<T>
{
    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector)
    {
        return CreateComparer(keySelector, null);
    }

    public static IEqualityComparer<T> CreateComparer<V>(Func<T, V> keySelector, 
                                                         IEqualityComparer<V> comparer)
    {
        return new KeyEqualityComparer<V>(keySelector, comparer);
    }

    class KeyEqualityComparer<V> : IEqualityComparer<T>
    {
        readonly Func<T, V> keySelector;
        readonly IEqualityComparer<V> comparer;

        public KeyEqualityComparer(Func<T, V> keySelector, 
                                   IEqualityComparer<V> comparer)
        {
            if (keySelector == null)
                throw new ArgumentNullException("keySelector");

            this.keySelector = keySelector;
            this.comparer = comparer ?? EqualityComparer<V>.Default;
        }

        public bool Equals(T x, T y)
        {
            return comparer.Equals(keySelector(x), keySelector(y));
        }

        public int GetHashCode(T obj)
        {
            return comparer.GetHashCode(keySelector(obj));
        }
    }
}

Você pode usá -lo assim:

var comparer1 = Equality<Person>.CreateComparer(p => p.ID);
var comparer2 = Equality<Person>.CreateComparer(p => p.Name);
var comparer3 = Equality<Person>.CreateComparer(p => p.Birthday.Year);
var comparer4 = Equality<Person>.CreateComparer(p => p.Name, StringComparer.CurrentCultureIgnoreCase);

Pessoa é uma aula simples:

class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
}
public class FuncEqualityComparer<T> : IEqualityComparer<T>
{
    readonly Func<T, T, bool> _comparer;
    readonly Func<T, int> _hash;

    public FuncEqualityComparer( Func<T, T, bool> comparer )
        : this( comparer, t => t.GetHashCode())
    {
    }

    public FuncEqualityComparer( Func<T, T, bool> comparer, Func<T, int> hash )
    {
        _comparer = comparer;
        _hash = hash;
    }

    public bool Equals( T x, T y )
    {
        return _comparer( x, y );
    }

    public int GetHashCode( T obj )
    {
        return _hash( obj );
    }
}

Com extensões:-

public static class SequenceExtensions
{
    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer ) );
    }

    public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, Func<T, T, bool> comparer, Func<T, int> hash )
    {
        return first.SequenceEqual( second, new FuncEqualityComparer<T>( comparer, hash ) );
    }
}

A resposta de Orip é ótima.

Aqui um pequeno método de extensão para facilitar ainda mais:

public static IEnumerable<T> Distinct<T>(this IEnumerable<T> list, Func<T, object>    keyExtractor)
{
    return list.Distinct(new KeyEqualityComparer<T>(keyExtractor));
}
var distinct = foo.Distinct(x => x.ToLower())

Vou responder minha própria pergunta. Para tratar os dicionários como conjuntos, o método mais simples parece ser aplicar operações definidas aos dit.keys e depois converter de volta aos dicionários com enumerável.todictionary (...).

A implementação em (texto alemão) Implementando a IEqualityCompare com a expressão lambdase preocupa com os valores nulos e usa métodos de extensão para gerar o IEquityComparer.

Para criar um IequalityCompare em uma união Linq, você só tem que escrever

persons1.Union(persons2, person => person.LastName)

O comparador:

public class LambdaEqualityComparer<TSource, TComparable> : IEqualityComparer<TSource>
{
  Func<TSource, TComparable> _keyGetter;

  public LambdaEqualityComparer(Func<TSource, TComparable> keyGetter)
  {
    _keyGetter = keyGetter;
  }

  public bool Equals(TSource x, TSource y)
  {
    if (x == null || y == null) return (x == null && y == null);
    return object.Equals(_keyGetter(x), _keyGetter(y));
  }

  public int GetHashCode(TSource obj)
  {
    if (obj == null) return int.MinValue;
    var k = _keyGetter(obj);
    if (k == null) return int.MaxValue;
    return k.GetHashCode();
  }
}

Você também precisa adicionar um método de extensão para suportar a inferência do tipo

public static class LambdaEqualityComparer
{
       // source1.Union(source2, lambda)
        public static IEnumerable<TSource> Union<TSource, TComparable>(
           this IEnumerable<TSource> source1, 
           IEnumerable<TSource> source2, 
            Func<TSource, TComparable> keySelector)
        {
            return source1.Union(source2, 
               new LambdaEqualityComparer<TSource, TComparable>(keySelector));
       }
   }

Apenas uma otimização: podemos usar o EqualityComparer pronta para uso para comparações de valor, em vez de delegá-lo.

Isso também tornaria a implementação mais limpa à medida que a lógica de comparação real permanece agora em gethashcode () e iguals () que você já pode ter sobrecarregado.

Aqui está o código:

public class MyComparer<T> : IEqualityComparer<T> 
{ 
  public bool Equals(T x, T y) 
  { 
    return EqualityComparer<T>.Default.Equals(x, y); 
  } 

  public int GetHashCode(T obj) 
  { 
    return obj.GetHashCode(); 
  } 
} 

Não se esqueça de sobrecarregar os métodos GethashCode () e Equals () em seu objeto.

Este post me ajudou: C# Compare dois valores genéricos

Sushil

Resposta de Orip é ótimo. Expandindo a resposta de Orip:

Eu acho que a chave da solução é usar "método de extensão" para transferir o "tipo anônimo".

    public static class Comparer 
    {
      public static IEqualityComparer<T> CreateComparerForElements<T>(this IEnumerable<T> enumerable, Func<T, object> keyExtractor)
      {
        return new KeyEqualityComparer<T>(keyExtractor);
      }
    }

Uso:

var n = ItemList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList();
n.AddRange(OtherList.Select(s => new { s.Vchr, s.Id, s.Ctr, s.Vendor, s.Description, s.Invoice }).ToList(););
n = n.Distinct(x=>new{Vchr=x.Vchr,Id=x.Id}).ToList();
public static Dictionary<TKey, TValue> Distinct<TKey, TValue>(this IEnumerable<TValue> items, Func<TValue, TKey> selector)
  {
     Dictionary<TKey, TValue> result = null;
     ICollection collection = items as ICollection;
     if (collection != null)
        result = new Dictionary<TKey, TValue>(collection.Count);
     else
        result = new Dictionary<TKey, TValue>();
     foreach (TValue item in items)
        result[selector(item)] = item;
     return result;
  }

Isso torna possível selecionar uma propriedade com lambda como esta: .Select(y => y.Article).Distinct(x => x.ArticleID);

Eu não conheço uma aula existente, mas algo como:

public class MyComparer<T> : IEqualityComparer<T>
{
  private Func<T, T, bool> _compare;
  MyComparer(Func<T, T, bool> compare)
  {
    _compare = compare;
  }

  public bool Equals(T x, Ty)
  {
    return _compare(x, y);
  }

  public int GetHashCode(T obj)
  {
    return obj.GetHashCode();
  }
}

Nota: Na verdade, eu não compilei e executei isso ainda, então pode haver um erro de digitação ou outro bug.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top