Pergunta

Examinei cerca de 20 exemplos sobre isso no SO e em outros lugares, mas não encontrei nenhum que cubra o que estou tentando fazer.Esse - Posso especificar meu comparador de tipo explícito embutido? - parece o que eu preciso, mas não vai longe o suficiente (ou não entendo como ir mais longe).

  • Eu tenho uma lista de LoadData, o objeto LoadData possui campos de tipos de referência e de valor
  • Precisa agrupar em uma mistura de campos de referência e valor, projetar a saída para um tipo anônimo
  • Precisa (eu acho) fornecer um IEqualityComparer personalizado para especificar como comparar os campos GroupBy, mas eles são do tipo anônimo

    private class LoadData
    {
        public PeriodEndDto PeriodEnd { get; set; }
        public ComponentDto Component { get; set; }
        public string GroupCode { get; set; }
        public string PortfolioCode { get; set; }
    }
    

A melhor consulta GroupBy que tenho até agora:

var distinctLoads = list.GroupBy(
    dl => new { PeriodEnd = dl.PeriodEnd, 
                Component = dl.Component, 
                GroupCode = dl.GroupCode },
    (key, data) => new {PeriodEnd = key.PeriodEnd, 
                Component = key.Component, 
                GroupCode = key.GroupCode, 
                PortfolioList = data.Select(d=>d.PortfolioCode)
                                    .Aggregate((g1, g2) => g1 + "," + g2)},
    null);

Este grupo, mas ainda há duplicatas.

  1. Como posso especificar um código personalizado para comparar os campos GroupBy?Por exemplo, os Componentes poderiam ser comparados por Component.Code.
Foi útil?

Solução

O problema aqui é que seu tipo de chave é anônimo, o que significa que você não pode declarar uma classe que implemente IEqualityComparer<T> para esse tipo de chave.Embora fosse possível escrever um comparador que comparasse tipos anônimos quanto à igualdade de maneira personalizada (por meio de um método genérico, delegados e inferência de tipo), não seria muito agradável.

As duas opções mais simples são provavelmente:

  • Faça o tipo anônimo "simplesmente funcionar" substituindo Equals/GetHashCode em PeriodEndDto e ComponentDto.Se houver uma igualdade natural que você gostaria de usar em todos os lugares, esta é provavelmente a opção mais sensata.Eu recomendaria implementar IEquatable<T> também
  • Não use um tipo anônimo para agrupamento - use um tipo nomeado e então você pode substituir GetHashCode e Equals sobre isso, ou você pode escrever um comparador de igualdade personalizado da maneira normal.

EDITAR: ProjectionEqualityComparer realmente não funcionaria.Seria viável escrever algo semelhante - uma espécie de CompositeEqualityComparer o que permitiu criar um comparador de igualdade a partir de vários pares "projeção + comparador".Seria muito feio comparado com o tipo anônimo.

Outras dicas

EDITAR:

Como aponta Jon Skeet, esta solução parece melhor do que é, se você não pensar muito sobre isso, porque esqueci de implementar GetHashCode.Ter que implementar o GethashCode torna essa abordagem, como Jon diz em sua resposta: "Não terrivelmente agradável". Presumivelmente, essa também é a explicação para a ausência (chamada "inexplicável") de EqualityComparer<T>.Create() no quadro.Deixarei a resposta para referência, pois exemplos do que não fazer também podem ser instrutivos.

RESPOSTA ORIGINAL:

Você poderia usar a abordagem sugerida pelo Comparer<T>.Create padrão introduzido no .NET 4.5 (mas inexplicavelmente ausente no EqualityComparer<T>).Para fazer isso, crie um DelegateEqualityComparer<T> aula:

class DelegateEqualityComparer<T> : EqualityComparer<T>
{
    private readonly Func<T, T, bool> _equalityComparison;

    private DelegateEqualityComparer(Func<T, T, bool> equalityComparison)
    {
        if (equalityComparison == null)
            throw new ArgumentNullException("equalityComparison");
        _equalityComparison = equalityComparison;
    }

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

    public static DelegateEqualityComparer<T> Create(
        Func<T, T, bool> equalityComparison)
    {
        return new DelegateEqualityComparer<T>(equalityComparison);
    }
}

Em seguida, escreva wrappers em torno dos métodos GroupBy para aceitar um Func<TKey, TKey, bool> delegado no lugar do IEqualityComparer<TKey> parâmetro.Esses métodos envolvem o delegado em um DelegateEqualityComparer<T> instância e passe-a para o método GroupBy correspondente.Exemplo:

public static class EnumerableExt
{
    public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(
        this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector,
        Func<TKey, TKey, bool> equalityComparison)
    {
        return source.GroupBy(
            keySelector,
            DelegateEqualityComparer<TKey>.Create(equalityComparison);
    }
}

Finalmente, no site da chamada, você usaria algo como esta expressão para o equalityComparison argumento:

(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
    && a.Component.Code.Equals(b.Component.Code)
    && a.GroupCode.Equals(b.GroupCode)
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top