LINQ’s Distinct() op een bepaalde eigenschap

Ik speel met LINQ om er meer over te leren, maar ik weet niet hoe ik Distinctmoet gebruiken als ik geen eenvoudige lijst heb (een eenvoudige lijst met gehele getallen is vrij eenvoudig te doen , dit is niet de vraag). Wat ik als ik Distinctop een lijst van een object op éénof meereigenschappen van het object?

Voorbeeld: als een object Personis, met eigenschap Id. Hoe kan ik alle Persoon krijgen en Distincterop gebruiken met de eigenschap Idvan het object?

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

Hoe kan ik alleen Person1en Person3krijgen? Is dat mogelijk?

Als het niet mogelijk is met LINQ, wat is dan de beste manier om een lijst met Personte hebben, afhankelijk van enkele eigenschappen in .NET 3.5?


Antwoord 1, autoriteit 100%

EDIT: dit maakt nu deel uit van MoreLINQ.

Wat je nodig hebt, is een effectieve “onderscheiden-door”. Ik geloof niet dat het deel uitmaakt van LINQ zoals het is, hoewel het vrij eenvoudig is om te schrijven:

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

Dus om de afzonderlijke waarden te vinden met alleen de eigenschap Id, kunt u het volgende gebruiken:

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

En om meerdere eigenschappen te gebruiken, kunt u anonieme typen gebruiken, die gelijkheid op de juiste manier implementeren:

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

Niet getest, maar het zou moeten werken (en het compileert nu tenminste).

Het gaat echter uit van de standaardvergelijker voor de sleutels – als u een gelijkheidsvergelijker wilt doorgeven, geeft u deze gewoon door aan de HashSet-constructor.


Antwoord 2, autoriteit 97%

Wat als ik een afzonderlijke lijst wil verkrijgen op basis van éénof meereigenschappen?

Eenvoudig! Je wilt ze groeperen en een winnaar uit de groep kiezen.

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

Als u groepen op meerdere eigendommen wilt definiëren, gaat u als volgt te werk:

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

Opmerking: bepaalde query-providers kunnen niet oplossen dat elke groep ten minste één element moet hebben en dat First de juiste methode is om in die situatie aan te roepen. Als u merkt dat u met zo’n vraagprovider werkt, kan FirstOrDefault helpen om uw vraag via de zoekprovider te krijgen.


Antwoord 3, autoriteit 7%

Gebruik:

List<Person> pList = new List<Person>();
/* Fill list */
var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());

De wherehelpt je bij het filteren van de ingangen (kan ingewikkelder zijn) en de groupbyen selectvoeren de distinct-functie uit.


Antwoord 4, autoriteit 6%

Je kunt ook de query-syntaxis gebruiken als je wilt dat het er helemaal LINQ-achtig uitziet:

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

Antwoord 5, autoriteit 5%

Ik denk dat het genoeg is:

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

Antwoord 6, autoriteit 3%

Oplossing groepeer eerst op uw velden en selecteer vervolgens het eerste standaarditem.

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

Antwoord 7, autoriteit 2%

U kunt dit doen met de standaard Linq.ToLookup(). Hiermee wordt een verzameling waarden gemaakt voor elke unieke sleutel. Selecteer gewoon het eerste item in de collectie

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

Antwoord 8

De volgende code is functioneel gelijk aan Jon Skeet’s antwoord.

Getest op .NET 4.5, zou moeten werken op elke eerdere versie van LINQ.

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

Bekijk trouwens Jon Skeet’s nieuwste versie van DistinctBy.cs op Google Code.


Antwoord 9

Ik heb een artikel geschreven waarin wordt uitgelegd hoe u de functie Distinct kunt uitbreiden, zodat u het volgende kunt doen:

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 is het artikel (nu in het webarchief): Uitbreiding van LINQ – Een eigenschap specificeren in de verschillende functie


Antwoord 10

Persoonlijk gebruik ik de volgende 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();
    }
}

Dan een uitbreidingsmethode:

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

Tot slot het beoogde gebruik:

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

Het voordeel dat ik vond bij het gebruik van deze aanpak is het hergebruik van de klasse LambdaEqualityComparervoor andere methoden die een IEqualityCompareraccepteren. (Oh, en ik laat de yielddingen over aan de originele LINQ-implementatie…)


Antwoord 11

U kunt DistinctBy() gebruiken om afzonderlijke records op te halen met een objecteigenschap. Voeg gewoon de volgende verklaring toe voordat u deze gebruikt:

met behulp van Microsoft.Ajax.Utilities;

en gebruik het dan als volgt:

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

waarbij ‘Index’ de eigenschap is waarvan ik wil dat de gegevens verschillend zijn.


Antwoord 12

Als je een Distinct-methode voor meerdere eigenschappen nodig hebt, kun je mijn PowerfulExtensionsbekijken bibliotheek. Momenteel bevindt het zich in een zeer jonge fase, maar u kunt nu al methoden gebruiken zoals Distinct, Union, Intersect, Behalve op een willekeurig aantal eigenschappen;

Zo gebruik je het:

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

Antwoord 13

Toen we in ons project voor een dergelijke taak stonden, hebben we een kleine API gedefinieerd om vergelijkers samen te stellen.

Dus de use case was als volgt:

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

En API zelf ziet er zo uit:

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

Meer details vindt u op onze site: IEqualityComparer in LINQ.


Antwoord 14

Je kunt het (zij het niet bliksemsnel) als volgt doen:

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

Dat wil zeggen: “selecteer alle mensen waarvan er geen andere persoon in de lijst staat met dezelfde ID.”

Let op, in jouw voorbeeld zou dat gewoon persoon 3 selecteren. Ik weet niet zeker hoe ik moet zeggen welke je wilt, van de vorige twee.


Antwoord 15

Als u de MoreLinq-bibliotheek niet aan uw project wilt toevoegen om de DistinctBy-functionaliteit te krijgen, kunt u hetzelfde eindresultaat krijgen door de overbelasting van Linq’s Distinctmethode die een IEqualityComparer-argument gebruikt.

U begint met het maken van een Generic Aangepaste gelijkheidscategorie die Lambda-syntaxis gebruikt om aangepaste vergelijking van twee instanties van een generieke klasse te verrichten:

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

Dan in uw hoofdcode gebruikt u het zoals SO:

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! 🙂

Het bovenstaande veronderstelt het volgende:

  • Property Person.Idis van het type int
  • De peopleCollectie bevat geen nul-elementen

Als de collectie nulls kan bevatten, herschrijf dan eenvoudig de lambdas om te controleren op null, b.v.:

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

bewerken

Deze aanpak is vergelijkbaar met die in het antwoord van Vladimir Nesterovsky, maar eenvoudiger.

Het is ook vergelijkbaar met die in het antwoord van Joel, maar zorgt voor complexe vergelijkingslogica met meerdere eigenschappen.

Als uw objecten echter alleen kunnen verschillen door Id, gaf een andere gebruiker het juiste antwoord dat alles wat u hoeft te doen, de standaard implementaties van GetHashCode()heeft overschreden en Equals()in uw Personklasse en gebruik vervolgens de out-of-the-box Distinct()METHODE VAN LINQ om uit te filteren alle duplicaten.


Antwoord 16

Met de snel-te-rele uitgaven .NET 6, is er nieuwe oplossing met behulp van DE NIEUWE DistinctBy()Uitbreiding in LINQ , dus beginnend met .NET 6, kunnen wij

doen

var distinctPersonsById = personList.DistinctBy(x => x.Id);

Antwoord 17

De beste manier om dit te doen die compatibel zal zijn met andere .NET-versies is om gelijken en Gethash te negeren om dit aan te pakken (zie Stack Overloop Vraag Deze code retourneert verschillende waarden. Wat ik wil, is echter een sterk getypte collectie retourneren in tegenstelling tot een anoniem type ), maar als u iets nodig heeft Dat is generiek in uw hele code, de oplossingen in dit artikel zijn geweldig.


Antwoord 18

Override gelijken (object obj) en Gethashcode () methoden:

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

en bel dan gewoon:

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

Antwoord 19

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

Antwoord 20

Je zou Equals op persoon moeten kunnen overschrijven om Equals daadwerkelijk te kunnen doen op Person.id. Dit zou moeten resulteren in het gedrag dat u zoekt.


Antwoord 21

Als u een oude .NET-versie gebruikt, waar de extensiemethode niet is ingebouwd, kunt u uw eigen extensiemethode definiëren:

public static class EnumerableExtensions
{
    public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> enumerable, Func<T, TKey> keySelector)
    {
        return enumerable.GroupBy(keySelector).Select(grp => grp.First());
    }
}

Voorbeeld van gebruik:

var personsDist = persons.DistinctBy(item => item.Name);

Antwoord 22

Probeer het eens met onderstaande code.

var Item = GetAll().GroupBy(x => x .Id).ToList();

Other episodes