Pregunta

Así que revisé unos 20 ejemplos sobre esto en SO y en otros lugares, pero no encontré ninguno que cubra lo que estoy tratando de hacer.Este - ¿Puedo especificar mi comparador de tipo explícito en línea? - parece lo que necesito, pero no va lo suficientemente lejos (o no entiendo cómo llevarlo más lejos).

  • Tengo una lista de LoadData, el objeto LoadData tiene campos de tipo referencia y valor
  • Es necesario agrupar en una combinación de campos de referencia y valor, proyectar la salida a un tipo anónimo
  • Necesito (creo) proporcionar un IEqualityComparer personalizado para especificar cómo comparar los campos GroupBy, pero son de 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; }
    }
    

La mejor consulta GroupBy que tengo hasta ahora:

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

Esto se agrupa, pero todavía hay duplicados.

  1. ¿Cómo puedo especificar un código personalizado para comparar los campos GroupBy?Por ejemplo, los componentes podrían compararse mediante Component.Code.
¿Fue útil?

Solución

El problema aquí es que su tipo de clave es anónima, lo que significa que no puede declarar una clase que implemente IEqualityComparer<T> para ese tipo de clave.mientras seria posible escribir un comparador que comparara la igualdad de tipos anónimos de una manera personalizada (a través de un método genérico, delegados e inferencia de tipos), no sería muy agradable.

Probablemente las dos opciones más sencillas sean:

  • Haga que el tipo anónimo "simplemente funcione" anulando Equals/GetHashCode en PeriodEndDto y ComponentDto.Si existe una igualdad natural que le gustaría utilizar en todas partes, esta es probablemente la opción más sensata.Recomiendo implementar IEquatable<T> también
  • No utilice un tipo anónimo para agrupar: utilice un tipo con nombre y luego podrá anularlo GetHashCode y Equals sobre eso, o podrías escribir un comparador de igualdad personalizado de la forma normal.

EDITAR: ProjectionEqualityComparer realmente no funcionaría.Sin embargo, sería factible escribir algo similar: una especie de CompositeEqualityComparer lo que le permitió crear un comparador de igualdad a partir de varios pares de "proyección + comparador".Sin embargo, sería bastante feo comparado con el tipo anónimo.

Otros consejos

EDITAR:

Como señala Jon Skeet, esta solución parece mejor de lo que es, si no lo piensas demasiado, porque me olvidé de implementar GetHashCode.Tener que implementar Gethashcode hace este enfoque, como dice Jon en su respuesta, "no es terriblemente agradable". Presumiblemente, esta es también la explicación de la (llamada "inexplicable") ausencia de EqualityComparer<T>.Create() en el marco.Dejaré la respuesta como referencia, ya que los ejemplos de lo que no se debe hacer también pueden ser instructivos.

RESPUESTA ORIGINAL:

Podría utilizar el enfoque sugerido por el Comparer<T>.Create patrón introducido en .NET 4.5 (pero inexplicablemente ausente en EqualityComparer<T>).Para ello, cree un DelegateEqualityComparer<T> clase:

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

Luego escriba envoltorios alrededor de los métodos GroupBy para aceptar un Func<TKey, TKey, bool> delegado en lugar del IEqualityComparer<TKey> parámetro.Estos métodos envuelven al delegado en un DelegateEqualityComparer<T> instancia y pasarla al método GroupBB correspondiente.Ejemplo:

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, en su sitio de llamada, usaría algo como esta expresión para el equalityComparison argumento:

(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
    && a.Component.Code.Equals(b.Component.Code)
    && a.GroupCode.Equals(b.GroupCode)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top