LINQ GroupBy su più ref campi di tipo;Personalizzato EqualityComparer
-
13-12-2019 - |
Domanda
Così ho guardato attraverso circa 20 esempi in questo MODO e altrove, ma non ho trovato uno che copre quello che sto cercando di fare.Questo - Posso specificare il mio esplicito tipo di comparatore inline? - sembra che ciò che ho bisogno, ma non va abbastanza lontano (o io non riesco a capire come fare di più).
- Ho un Elenco di LoadData, il LoadData oggetto ha dei campi di riferimento e i tipi di valore
- Necessità di gruppo su una miscela di ref e campi di valore di progetto, l'uscita di un tipo anonimo
Bisogno (credo) per fornire un custom IEqualityComparer per specificare come confrontare il GroupBy campi, ma sono un tipo anonimo
private class LoadData { public PeriodEndDto PeriodEnd { get; set; } public ComponentDto Component { get; set; } public string GroupCode { get; set; } public string PortfolioCode { get; set; } }
I migliori GroupBy query che ho di andare così lontano:
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);
Questo gruppo, ma non ci sono ancora duplicati.
- Come posso specificare un codice personalizzato per confrontare GroupBy campi?Per esempio, i Componenti potrebbero essere confrontate Componente.Codice.
Soluzione
Il problema qui è che il vostro tipo di chiave è anonimo, il che significa che non è possibile dichiarare una classe che implementa IEqualityComparer<T>
per che tipo di chiave.Mentre sarebbe possibile per scrivere un comparatore che confronta tipi anonimi per la parità in modo personalizzato (tramite un metodo generico, delegati e il tipo di inferenza), non sarebbe terribilmente piacevole.
Le due opzioni più semplici sono probabilmente:
- Fare il tipo anonimo "sufficiente" entro l'override del metodo Equals/GetHashCode in
PeriodEndDto
eComponentDto
.Se c'è una parità di diritti che si vuole utilizzare ovunque, questo è probabilmente l'opzione più sana.Mi consiglia di attuazioneIEquatable<T>
così - Non utilizzare un tipo anonimo per il raggruppamento di usare un nome di un tipo, e quindi è possibile eseguire l'override
GetHashCode
eEquals
su questo, o si potrebbe scrivere un custom operatore di uguaglianza in modo normale.
EDIT: ProjectionEqualityComparer
non funziona davvero.Sarebbe fattibile per scrivere qualcosa di simile, però, in una sorta di CompositeEqualityComparer
che permette di creare un operatore di uguaglianza di diversi "proiezione + comparatore di" coppie.Sarebbe abbastanza brutto rispetto al tipo anonimo, però.
Altri suggerimenti
EDIT:
Come Jon Skeet, la soluzione sembra migliore di quello che è, se non pensare troppo su di esso, perché ho dimenticato di implementare GetHashCode.La necessità di implementare GetHashCode rende questo approccio, come Jon dice nella sua risposta, "non è terribilmente piacevole." Presumibilmente, questa è anche la spiegazione per il (cosiddetto "inspiegabile") assenza di EqualityComparer<T>.Create()
nel quadro.Lascio la risposta a riferimento, come esempio di cosa non fare, può essere utile pure.
ORIGINALE RISPOSTA:
Si potrebbe utilizzare l'approccio suggerito dal Comparer<T>.Create
modello introdotto nel .NET 4.5 (ma inspiegabilmente assenti in EqualityComparer<T>
).Per farlo, creare un DelegateEqualityComparer<T>
classe:
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);
}
}
Quindi scrivere wrapper intorno GroupBy metodi per accettare un Func<TKey, TKey, bool>
delegato al posto del IEqualityComparer<TKey>
parametro.Questi metodi a capo il delegato in un DelegateEqualityComparer<T>
istanza, e che passare al corrispondente GroupBy metodo.Esempio:
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);
}
}
Infine, presso il sito di chiamata, utilizzare qualcosa di simile a questa espressione per l' equalityComparison
argomento:
(a, b) => a.PeriodEnd.Equals(b.PeriodEnd)
&& a.Component.Code.Equals(b.Component.Code)
&& a.GroupCode.Equals(b.GroupCode)