it-swarm.com.de

LINQs Distinct () für eine bestimmte Eigenschaft

Ich spiele mit LINQ, um mehr darüber zu erfahren, aber ich kann nicht herausfinden, wie Distinct verwendet wird, wenn ich keine einfache Liste habe (eine einfache Liste von Ganzzahlen ist ziemlich einfach, das ist nicht die Frage). Was ich verwenden möchte Distinct auf einer Liste eines Objekts auf einem oder Weitere Eigenschaften des Objekts?

Beispiel: Wenn ein Objekt Person ist, mit der Eigenschaft Id. Wie kann ich alle Personen abrufen und Distinct für sie mit der Eigenschaft Id des Objekts verwenden?

Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"

Wie kann ich nur Person1 und Person3 bekommen? Ist das möglich?

Wenn dies mit LINQ nicht möglich ist, wie kann eine Liste von Person in Abhängigkeit von einigen ihrer Eigenschaften in .NET 3.5 am besten erstellt werden?

965

EDIT: Dies ist jetzt Teil von MoreLINQ .

Was Sie brauchen, ist ein "Unterscheidungsmerkmal" effektiv. Ich glaube nicht, dass es Teil von LINQ ist, obwohl es ziemlich einfach zu schreiben ist:

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

Um die unterschiedlichen Werte nur mit der Eigenschaft Id zu ermitteln, können Sie Folgendes verwenden:

var query = people.DistinctBy(p => p.Id);

Um mehrere Eigenschaften zu verwenden, können Sie anonyme Typen verwenden, die die Gleichheit entsprechend implementieren:

var query = people.DistinctBy(p => new { p.Id, p.Name });

Ungetestet, aber es sollte funktionieren (und es wird jetzt zumindest kompiliert).

Es wird jedoch der Standardvergleich für die Schlüssel vorausgesetzt. Wenn Sie einen Gleichheitsvergleich übergeben möchten, übergeben Sie ihn einfach an den Konstruktor HashSet.

1118
Jon Skeet

Was ist, wenn ich eine eindeutige Liste basierend auf eins oder mehr Eigenschaften erhalten möchte?

Einfach! Sie möchten sie gruppieren und einen Gewinner aus der Gruppe auswählen.

List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.First())
  .ToList();

Wenn Sie Gruppen für mehrere Eigenschaften definieren möchten, gehen Sie wie folgt vor:

List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.First())
  .ToList();
1688
Amy B

Sie können auch die Abfragesyntax verwenden, wenn Sie möchten, dass alles wie in LINQ aussieht:

var uniquePeople = from p in people
                   group p by new {p.ID} //or group by new {p.ID, p.Name, p.Whatever}
                   into mygroup
                   select mygroup.FirstOrDefault();
72
Chuck Rostance

Verwenden:

List<Person> pList = new List<Person>();
/* Fill list */

var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

Die where hilft Ihnen beim Filtern der Einträge (könnte komplexer sein) und die groupby und select führen die unterschiedliche Funktion aus.

65
karcsi

Ich denke es ist genug:

list.Select(s => s.MyField).Distinct();
56
Ivan

Lösen Sie die erste Gruppe nach Ihren Feldern und wählen Sie dann firstordefault item aus.

    List<Person> distinctPeople = allPeople
   .GroupBy(p => p.PersonId)
   .Select(g => g.FirstOrDefault())
   .ToList();
35
cahit beyaz

Sie können dies mit dem Standard Linq.ToLookup() tun. Dadurch wird eine Sammlung von Werten für jeden eindeutigen Schlüssel erstellt. Wählen Sie einfach den ersten Artikel in der Sammlung

Persons.ToLookup(p => p.Id).Select(coll => coll.First());
24
David Fahlander

Der folgende Code ist funktional äquivalent zu Jon Skeets Antwort .

Getestet unter .NET 4.5, sollte auf allen früheren LINQ-Versionen funktionieren.

public static IEnumerable<TSource> DistinctBy<TSource, TKey>(
  this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
  HashSet<TKey> seenKeys = new HashSet<TKey>();
  return source.Where(element => seenKeys.Add(keySelector(element)));
}

Im Übrigen lesen Sie Jon Skeets neueste Version von DistinctBy.cs in Google Code .

16
Contango

Ich habe einen Artikel geschrieben, in dem erklärt wird, wie die Distinct-Funktion erweitert wird, damit Sie wie folgt vorgehen können:

var people = new List<Person>();

people.Add(new Person(1, "a", "b"));
people.Add(new Person(2, "c", "d"));
people.Add(new Person(1, "a", "b"));

foreach (var person in people.Distinct(p => p.ID))
    // Do stuff with unique list here.

Hier ist der Artikel: Erweitern von LINQ - Angeben einer Eigenschaft in der eindeutigen Funktion

11
Timothy Khouri

Du kannst es (wenn auch nicht blitzschnell) so machen:

people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));

Das heißt: "Wählen Sie alle Personen aus, bei denen sich keine andere Person mit derselben ID in der Liste befindet."

Wohlgemerkt, in Ihrem Beispiel würde dies nur Person 3 auswählen. Ich bin nicht sicher, wie ich sagen soll, welche von den beiden vorherigen Sie wollen.

5
mqp

Persönlich benutze ich folgende Klasse:

public class LambdaEqualityComparer<TSource, TDest> : 
    IEqualityComparer<TSource>
{
    private Func<TSource, TDest> _selector;

    public LambdaEqualityComparer(Func<TSource, TDest> selector)
    {
        _selector = selector;
    }

    public bool Equals(TSource obj, TSource other)
    {
        return _selector(obj).Equals(_selector(other));
    }

    public int GetHashCode(TSource obj)
    {
        return _selector(obj).GetHashCode();
    }
}

Dann eine Erweiterungsmethode:

public static IEnumerable<TSource> Distinct<TSource, TCompare>(
    this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
    return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}

Schließlich ist der Verwendungszweck:

var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);

Der Vorteil, den ich bei diesem Ansatz festgestellt habe, ist die Wiederverwendung der Klasse LambdaEqualityComparer für andere Methoden, die eine IEqualityComparer akzeptieren. (Oh, und ich überlasse das yield Zeug der ursprünglichen LINQ-Implementierung ...)

5
Joel

Wenn Sie für mehrere Eigenschaften eine Distinct-Methode benötigen, können Sie meine PowerfulExtensions -Bibliothek überprüfen. Derzeit befindet es sich in einem sehr jungen Stadium, aber Sie können bereits Methoden wie Distinct, Union, Intersect und Except für eine beliebige Anzahl von Eigenschaften verwenden.

So verwenden Sie es:

using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
5
Andrzej Gis

Als wir uns in unserem Projekt einer solchen Aufgabe stellten, definierten wir eine kleine API, um Komparatoren zu erstellen.

Der Anwendungsfall war also wie folgt:

var wordComparer = KeyEqualityComparer.Null<Word>().
    ThenBy(item => item.Text).
    ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);

Und API selbst sieht so aus:

using System;
using System.Collections;
using System.Collections.Generic;

public static class KeyEqualityComparer
{
    public static IEqualityComparer<T> Null<T>()
    {
        return null;
    }

    public static IEqualityComparer<T> EqualityComparerBy<T, K>(
        this IEnumerable<T> source,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc);
    }

    public static KeyEqualityComparer<T, K> ThenBy<T, K>(
        this IEqualityComparer<T> equalityComparer,
        Func<T, K> keyFunc)
    {
        return new KeyEqualityComparer<T, K>(keyFunc, equalityComparer);
    }
}

public struct KeyEqualityComparer<T, K>: IEqualityComparer<T>
{
    public KeyEqualityComparer(
        Func<T, K> keyFunc,
        IEqualityComparer<T> equalityComparer = null)
    {
        KeyFunc = keyFunc;
        EqualityComparer = equalityComparer;
    }

    public bool Equals(T x, T y)
    {
        return ((EqualityComparer == null) || EqualityComparer.Equals(x, y)) &&
                EqualityComparer<K>.Default.Equals(KeyFunc(x), KeyFunc(y));
    }

    public int GetHashCode(T obj)
    {
        var hash = EqualityComparer<K>.Default.GetHashCode(KeyFunc(obj));

        if (EqualityComparer != null)
        {
            var hash2 = EqualityComparer.GetHashCode(obj);

            hash ^= (hash2 << 5) + hash2;
        }

        return hash;
    }

    public readonly Func<T, K> KeyFunc;
    public readonly IEqualityComparer<T> EqualityComparer;
}

Weitere Informationen finden Sie auf unserer Website: IEqualityComparer in LINQ.

Wenn Sie die MoreLinq-Bibliothek nicht zu Ihrem Projekt hinzufügen möchten, um die Funktion DistinctBy zu erhalten, können Sie dasselbe Endergebnis mit der Überladung der Methode Distinct von Linq erzielen, die ein Argument IEqualityComparer enthält.

Zunächst erstellen Sie eine generische benutzerdefinierte Gleichheitsvergleichsklasse, die mithilfe der Lambda-Syntax einen benutzerdefinierten Vergleich zweier Instanzen einer generischen Klasse durchführt:

public class CustomEqualityComparer<T> : IEqualityComparer<T>
{
    Func<T, T, bool> _comparison;
    Func<T, int> _hashCodeFactory;

    public CustomEqualityComparer(Func<T, T, bool> comparison, Func<T, int> hashCodeFactory)
    {
        _comparison = comparison;
        _hashCodeFactory = hashCodeFactory;
    }

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

    public int GetHashCode(T obj)
    {
        return _hashCodeFactory(obj);
    }
}

Dann verwenden Sie es in Ihrem Hauptcode wie folgt:

Func<Person, Person, bool> areEqual = (p1, p2) => int.Equals(p1.Id, p2.Id);

Func<Person, int> getHashCode = (p) => p.Id.GetHashCode();

var query = people.Distinct(new CustomEqualityComparer<Person>(areEqual, getHashCode));

Voila! :)

Das Obige setzt Folgendes voraus:

  • Die Eigenschaft Person.Id ist vom Typ int
  • Die Auflistung people enthält keine Null-Elemente

Wenn die Sammlung Nullen enthalten könnte, schreiben Sie die Lambdas einfach neu, um nach Nullen zu suchen, z.

Func<Person, Person, bool> areEqual = (p1, p2) => 
{
    return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};

EDIT

Dieser Ansatz ähnelt dem in Vladimir Nesterovskys Antwort, ist aber einfacher.

Sie ähnelt auch der Antwort in Joel, ermöglicht jedoch eine komplexe Vergleichslogik mit mehreren Eigenschaften.

Wenn sich Ihre Objekte jedoch immer nur durch Id unterscheiden können, hat ein anderer Benutzer die richtige Antwort gegeben. Sie müssen lediglich die Standardimplementierungen von GetHashCode() und Equals() in Ihrer Klasse Person überschreiben und dann einfach die verwenden Out-of-the-Box-Methode Distinct() von Linq zum Herausfiltern von Duplikaten.

3
Caspian Canuck
List<Person>lst=new List<Person>
        var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
2
Arindam

Überschreiben Sie die Methoden Equals (object obj) und GetHashCode ():

class Person
{
    public int Id { get; set; }
    public int Name { get; set; }

    public override bool Equals(object obj)
    {
        return ((Person)obj).Id == Id;
        // or: 
        // var o = (Person)obj;
        // return o.Id == Id && o.Name == Name;
    }
    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

und dann einfach anrufen:

List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();

Der beste Weg, dies zu tun, der mit anderen .NET-Versionen kompatibel ist, besteht darin, Equals und GetHash zu überschreiben, um dies zu handhaben (siehe Frage zum Stapelüberlauf Dieser Code gibt unterschiedliche Werte zurück.) Ich möchte, dass eine stark typisierte Auflistung im Gegensatz zu einem anonymen Typ zurückgegeben wird.). Wenn Sie jedoch etwas benötigen, das im gesamten Code generisch ist, sind die Lösungen in diesem Artikel großartig.

2
gcoleman0828

Sie können DistinctBy () verwenden, um eindeutige Datensätze für eine Objekteigenschaft abzurufen. Fügen Sie einfach die folgende Anweisung hinzu, bevor Sie sie verwenden:

verwenden von Microsoft.Ajax.Utilities;

und benutze es dann wie folgt:

var listToReturn = responseList.DistinctBy(x => x.Index).ToList();

dabei ist 'Index' die Eigenschaft, für die die Daten eindeutig sein sollen.

1
Harry .Naeem

Sie sollten in der Lage sein, Equals on person zu überschreiben, um Equals on Person.id tatsächlich auszuführen. Dies sollte zu dem Verhalten führen, das Sie suchen.

0
GWLlosa
    List<string> colors = new List<string> { "blue", "red", "black", "blue", "yellow", "blue" };
    IEnumerable<string> distinctColors = colors.Distinct();
0
uguronline