839 Stimmen

Distinct() mit Lambda?

Richtig, ich habe also eine Aufzählung und möchte verschiedene Werte daraus erhalten.

Verwendung von System.Linq gibt es natürlich eine Erweiterungsmethode namens Distinct . Im einfachen Fall kann es ohne Parameter verwendet werden, wie:

var distinctValues = myStringList.Distinct();

Schön und gut, aber wenn ich eine Aufzählung von Objekten habe, für die ich Gleichheit angeben muss, ist die einzige verfügbare Überladung:

var distinctValues = myCustomerList.Distinct(someEqualityComparer);

Das Argument des Gleichheitsvergleichers muss eine Instanz von IEqualityComparer<T> . Das kann ich natürlich auch tun, aber es ist etwas langatmig und, nun ja, unübersichtlich.

Was ich erwartet hätte, ist eine Überladung, die ein Lambda, sagen wir ein Func<T, T, bool> :

var distinctValues = myCustomerList.Distinct((c1, c2) => c1.CustomerId == c2.CustomerId);

Weiß jemand, ob es eine solche Erweiterung oder eine gleichwertige Umgehung gibt? Oder übersehe ich etwas?

Gibt es alternativ dazu eine Möglichkeit, eine IEqualityComparer inline (mich blamieren)?

Update

Ich habe eine Antwort von Anders Hejlsberg auf eine Beitrag in einem MSDN-Forum zu diesem Thema. Er sagt:

Das Problem, auf das Sie stoßen werden, ist, dass beim Vergleich zweier Objekte gleich sind, müssen sie denselben GetHashCode-Rückgabewert haben (oder die Hash-Tabelle, die intern von Distinct verwendet wird, nicht korrekt funktionieren). Wir verwenden IEqualityComparer, weil es kompatible Implementierungen von Equals und GetHashCode in einer einzigen Schnittstelle zusammenfasst.

Ich denke, das macht Sinn.

2 Stimmen

Siehe stackoverflow.com/questions/1183403/ für eine Lösung mit GroupBy

0 Stimmen

Nein, das ergibt keinen Sinn - wie können zwei Objekte, die identische Werte enthalten, zwei verschiedene Hash-Codes zurückgeben?

1 Stimmen

Es könnte helfen - Lösung para .Distinct(new KeyEqualityComparer<Customer,string>(c1 => c1.CustomerId)) und erklären, warum GetHashCode() wichtig ist, damit es richtig funktioniert.

1129voto

Carlo Bos Punkte 11126
IEnumerable<Customer> filteredList = originalList
  .GroupBy(customer => customer.CustomerId)
  .Select(group => group.First());

18 Stimmen

Ausgezeichnet! Dies ist auch sehr einfach in einer Erweiterungsmethode zu kapseln, wie DistinctBy (oder sogar Distinct da die Signatur eindeutig sein wird).

3 Stimmen

Funktioniert bei mir nicht! <Die Methode 'First' kann nur als letzte Abfrageoperation verwendet werden. Erwägen Sie, stattdessen die Methode 'FirstOrDefault' in diesem Fall zu verwenden.> Auch wenn ich 'FirstOrDefault' ausprobiert habe, hat es nicht funktioniert.

74 Stimmen

@TorHaugen: Seien Sie sich nur bewusst, dass die Erstellung all dieser Gruppen mit Kosten verbunden ist. Dies kann die Eingabe nicht streamen und wird am Ende alle Daten puffern, bevor etwas zurückgegeben wird. Das mag für Ihre Situation natürlich nicht relevant sein, aber ich bevorzuge die Eleganz von DistinctBy :)

550voto

Jon Skeet Punkte 1325502

Es sieht für mich so aus, als wollten Sie DistinctBy de MehrLINQ . Sie können dann schreiben:

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

Hier ist eine gekürzte Version von DistinctBy (keine Nullitätsprüfung und keine Möglichkeit, einen eigenen Schlüsselvergleicher anzugeben):

public static IEnumerable<TSource> DistinctBy<TSource, TKey>
     (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> knownKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        if (knownKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

18 Stimmen

Ich wusste, dass Jon Skeet die beste Antwort geben würde, indem ich einfach den Titel des Beitrags las. Wenn es etwas mit LINQ zu tun hat, ist Skeet Ihr Mann. Lesen Sie "C# In Depth", um gottgleiches LINQ-Wissen zu erlangen.

2 Stimmen

Tolle Antwort!!! auch für alle VB_Complainer über die yield + extra lib, foreach kann umgeschrieben werden als return source.Where(element => knownKeys.Add(keySelector(element)));

1 Stimmen

Ich erhalte eine Exception stackoverflow.com/questions/13405568/ Wenn ich dieses DistinctBy in einer LinqToSQL-Abfrage verwende, funktioniert der folgende Code für mich. public static IEnumerable<T> DistinctBy<T>(this IEnumerable<T> list, Func<T, object> propertySelector) { return list.GroupBy(propertySelector).Select(x => x.FirstOrDefault()); }

48voto

Zum Abschluss . Ich denke, die meisten Menschen, die wie ich hierher gekommen sind, wollen die einfachste Lösung möglich ohne Verwendung von Bibliotheken und mit bestmöglicher Leistung .

(Die akzeptierte Gruppierung nach Methode halte ich für einen Overkill in Bezug auf die Leistung. )

Hier ist eine einfache Erweiterungsmethode unter Verwendung der IEqualityComparer Schnittstelle, die auch für Nullwerte funktioniert.

Verwendung:

var filtered = taskList.DistinctBy(t => t.TaskExternalId).ToArray();

Erweiterung Methode Code

public static class LinqExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> items, Func<T, TKey> property)
    {
        GeneralPropertyComparer<T, TKey> comparer = new GeneralPropertyComparer<T,TKey>(property);
        return items.Distinct(comparer);
    }   
}
public class GeneralPropertyComparer<T,TKey> : IEqualityComparer<T>
{
    private Func<T, TKey> expr { get; set; }
    public GeneralPropertyComparer (Func<T, TKey> expr)
    {
        this.expr = expr;
    }
    public bool Equals(T left, T right)
    {
        var leftProp = expr.Invoke(left);
        var rightProp = expr.Invoke(right);
        if (leftProp == null && rightProp == null)
            return true;
        else if (leftProp == null ^ rightProp == null)
            return false;
        else
            return leftProp.Equals(rightProp);
    }
    public int GetHashCode(T obj)
    {
        var prop = expr.Invoke(obj);
        return (prop==null)? 0:prop.GetHashCode();
    }
}

23voto

Arasu RRK Punkte 1060

Kurzschrift-Lösung

myCustomerList.GroupBy(c => c.CustomerId, (key, c) => c.FirstOrDefault());

1 Stimmen

Könnten Sie erläutern, warum dies verbessert wurde?

22voto

Vijay Nirmal Punkte 3936

Ab .Net 6 (Preview 7) oder später gibt es eine neue Build-in-Methode Enumerable.DistinctBy um dies zu erreichen.

var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId);

// With IEqualityComparer
var distinctValues = myCustomerList.DistinctBy(c => c.CustomerId, someEqualityComparer);

4 Stimmen

Dies sollte eine neue akzeptierte Antwort sein

0 Stimmen

@TKharaishvili die Frage ist getaggt mit c#-3.0 Diese Antwort ist zwar durchaus relevant, aber ob sie die akzeptierte Antwort ist, sei dahingestellt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X