c# - LINQ GroupBy en mĂșltiples campos de tipo ref; Custom EqualityComparer
group-by iequalitycomparer (2)
Así que he examinado alrededor de 20 ejemplos sobre esto en SO y en otros lugares, pero no he encontrado uno que cubra lo que estoy tratando de hacer. Esto - ¿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 allá).
- Tengo una lista de LoadData, el objeto LoadData tiene campos de ambos tipos de 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 de GroupBy que he hecho 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);
Este grupo, pero todavía hay duplicados.
- ¿Cómo puedo especificar un código personalizado para comparar los campos GroupBy? Por ejemplo, los Componentes podrían ser comparados por Component.Code.
EDITAR:
Como señala Jon Skeet, esta solución parece mejor de lo que es, si no lo piensas demasiado, porque he olvidado implementar GetHashCode. Tener que implementar GetHashCode hace que este enfoque, como dice Jon en su respuesta, "no sea 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, como ejemplos de lo que no se debe hacer, también puede ser instructivo.
RESPUESTA ORIGINAL:
Podría usar el enfoque sugerido por el Comparer<T>.Create
patrón introducido en .NET 4.5 (pero inexplicablemente ausente en EqualityComparer<T>
). Para hacerlo, crea una clase DelegateEqualityComparer<T>
:
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 wrappers alrededor de los métodos GroupBy para aceptar un Func<TKey, TKey, bool>
delegate en lugar del parámetro IEqualityComparer<TKey>
. Estos métodos envuelven al delegado en una instancia DelegateEqualityComparer<T>
y lo pasan al método GroupBy 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 argumento de equalityComparison
comparación:
(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
&& a.Component.Code.Equals(b.Component.Code)
&& a.GroupCode.Equals(b.GroupCode)
El problema aquí es que su tipo de clave es anónimo, lo que significa que no puede declarar una clase que implemente IEqualityComparer<T>
para ese tipo de clave. Si bien sería posible escribir un comparador que comparara los tipos anónimos para la igualdad de una manera personalizada (a través de un método genérico, delega y escriba la inferencia), no sería terriblemente agradable.
Las dos opciones más simples son probablemente:
- Haga que el tipo anónimo "solo funcione" anulando Equals / GetHashCode en
PeriodEndDto
yComponentDto
. Si hay una igualdad natural que te gustaría usar en todas partes, esta es probablemente la opción más segura. Recomiendo implementarIEquatable<T>
también - No use un tipo anónimo para agrupar: use un tipo con nombre, y luego puede anular
GetHashCode
eEquals
sobre eso, o puede escribir un comparador de igualdad personalizado de la manera habitual.
EDITAR: ProjectionEqualityComparer
realmente no funcionaría. Sin embargo, sería posible escribir algo similar, una especie de CompositeEqualityComparer
que le permitió crear un comparador de igualdad a partir de varios pares de "proyección + comparación". Sin embargo, sería bastante feo comparado con el tipo anónimo.