LINQ ' s Distinct() na określonej własności
Bawię się LINQ, aby się o tym dowiedzieć, ale nie mogę rozgryźć, Jak używać Distinct
, gdy nie mam prostej listy (prosta lista liczb całkowitych jest dość łatwa do zrobienia, nie o to chodzi). Co jeśli chcę użyć Distinct na liście obiektu na Jeden lub więcej właściwości obiektu?
Przykład: jeśli obiekt jest Person
, z właściwością Id
. Jak Mogę uzyskać wszystkie osoby i używać Distinct
na nich z własnością Id
z obiekt?
Person1: Id=1, Name="Test1"
Person2: Id=1, Name="Test1"
Person3: Id=2, Name="Test2"
Jak mogę dostać tylko Person1
i Person3
? Czy to możliwe?
Jeśli nie jest to możliwe z LINQ, jaki byłby najlepszy sposób, aby mieć listę Person
w zależności od niektórych jego właściwości w. NET 3.5?
20 answers
EDIT : to jest teraz część MoreLINQ .
To, czego potrzebujesz, to "different-by" skutecznie. Nie wierzę, że jest to część LINQ w obecnym stanie, chociaż dość łatwo go napisać:
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;
}
}
}
Więc aby znaleźć różne wartości używając tylko właściwości Id
, możesz użyć:
var query = people.DistinctBy(p => p.Id);
I aby użyć wielu właściwości, możesz użyć anonimowych typów, które odpowiednio implementują równość:
var query = people.DistinctBy(p => new { p.Id, p.Name });
Nieprzetestowane, ale powinno zadziałać (a teraz przynajmniej kompilacji).
Zakłada domyślne porównywanie kluczy - jeśli chcesz przekazać porównywanie równości, po prostu przekaż je konstruktorowi HashSet
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-04 12:25:47
Proste! Chcesz je zgrupować i wybrać zwycięzcę z grupy.Co jeśli chcę uzyskać odrębną listę na podstawie jednej lub Więcej właściwości?
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.First())
.ToList();
Jeśli chcesz zdefiniować grupy na wielu właściwościach, oto jak to zrobić:
List<Person> distinctPeople = allPeople
.GroupBy(p => new {p.PersonId, p.FavoriteColor} )
.Select(g => g.First())
.ToList();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-14 19:46:58
Użycie:
List<Person> pList = new List<Person>();
/* Fill list */
var result = pList.Where(p => p.Name != null).GroupBy(p => p.Id).Select(grp => grp.FirstOrDefault());
where
pomaga filtrować wpisy (może być bardziej złożone), a groupby
i select
wykonują odrębną funkcję.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-12-31 02:33:57
Możesz również użyć składni zapytań, jeśli chcesz, aby wyglądała jak LINQ:
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();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-20 19:31:59
Myślę, że wystarczy:
list.Select(s => s.MyField).Distinct();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-23 14:54:26
Rozwiązanie pierwsza grupa przez pola następnie wybierz firstordefault item.
List<Person> distinctPeople = allPeople
.GroupBy(p => p.PersonId)
.Select(g => g.FirstOrDefault())
.ToList();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-07-13 08:33:18
Możesz to zrobić ze standardem Linq.ToLookup()
. Utworzy to zbiór wartości dla każdego unikalnego klucza. Po prostu wybierz pierwszy element w kolekcji
Persons.ToLookup(p => p.Id).Select(coll => coll.First());
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-16 19:07:45
Poniższy kod jest funkcjonalnie równoważny Odpowiedzi Jona Skeeta .
Testowany na. Net 4.5, powinien działać na wcześniejszych wersjach 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)));
}
/ Align = "center" bgcolor = "# e0ffe0 " / premier Wysp Owczych / / align = center / cs na Google Code .Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:47
Napisałem artykuł, który wyjaśnia, jak rozszerzyć odrębną funkcję, aby można było to zrobić w następujący sposób:
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.
Oto Artykuł (teraz w archiwum www): rozszerzenie LINQ-określenie właściwości w funkcji Distinct
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-09-11 19:14:16
Osobiście używam następującej klasy:
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();
}
}
Następnie metoda rozszerzenia:
public static IEnumerable<TSource> Distinct<TSource, TCompare>(
this IEnumerable<TSource> source, Func<TSource, TCompare> selector)
{
return source.Distinct(new LambdaEqualityComparer<TSource, TCompare>(selector));
}
Wreszcie, zamierzone użycie:
var dates = new List<DateTime>() { /* ... */ }
var distinctYears = dates.Distinct(date => date.Year);
Zaletą tego podejścia jest ponowne użycie klasy LambdaEqualityComparer
dla innych metod, które akceptują IEqualityComparer
. (Oh, i zostawiam yield
rzeczy do oryginalnej implementacji LINQ...)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-30 18:59:07
Możesz użyć DistinctBy () do uzyskania odrębnych rekordów przez właściwość obiektu. Wystarczy dodać następującą instrukcję przed jej użyciem:
Za Pomocą Microsoftu.Ajax.Utilities;
A następnie użyj go w następujący sposób:
var listToReturn = responseList.DistinctBy(x => x.Index).ToList();
Gdzie 'Index' jest właściwością, na której chcę, aby dane były odrębne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-03-27 06:04:44
Możesz to zrobić (choć nie błyskawicznie) w ten sposób:
people.Where(p => !people.Any(q => (p != q && p.Id == q.Id)));
To znaczy "wybierz wszystkie osoby, w których na liście nie ma innej innej osoby o tym samym identyfikatorze."
Pamiętaj, że w twoim przykładzie wystarczy wybrać osobę 3. Nie wiem, jak powiedzieć, co chcesz, z poprzednich dwóch.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-28 20:47:38
W przypadku, gdy potrzebujesz odrębnej metody na wielu właściwościach, możesz sprawdzić moją bibliotekę PowerfulExtensions . Obecnie jest to bardzo młody etap, ale już możesz używać metod takich jak Distinct, Union, Intersect, z wyjątkiem dowolnej liczby właściwości; {]}
Tak się go używa:
using PowerfulExtensions.Linq;
...
var distinct = myArray.Distinct(x => x.A, x => x.B);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-16 18:51:17
Kiedy stanęliśmy przed takim zadaniem w naszym projekcie, zdefiniowaliśmy małe API do komponowania komparatorów.
Więc przypadek użycia wyglądał tak:
var wordComparer = KeyEqualityComparer.Null<Word>().
ThenBy(item => item.Text).
ThenBy(item => item.LangID);
...
source.Select(...).Distinct(wordComparer);
A samo API wygląda tak:
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;
}
Więcej szczegółów znajduje się na naszej stronie: IEqualityComparer w LINQ.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-16 18:55:07
Jeśli nie chcesz dodawać Biblioteki MoreLinq do swojego projektu tylko po to, aby uzyskać funkcjonalność DistinctBy
, możesz uzyskać ten sam efekt końcowy, używając przeciążenia metody Distinct
Linq, która przyjmuje argument IEqualityComparer
.
Zaczynasz od stworzenia klasy porównującej równość, która używa składni lambda do porównywania dwóch instancji klasy generycznej:
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);
}
}
Następnie w głównym kodzie używasz go tak:
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! :)
The powyżej zakłada się, że:
- właściwość
Person.Id
jest typuint
- zbiór
people
nie zawiera żadnych elementów null
Jeśli kolekcja może zawierać null, po prostu przepisz lambdy, aby sprawdzić, czy nie ma null, np.:
Func<Person, Person, bool> areEqual = (p1, p2) =>
{
return (p1 != null && p2 != null) ? int.Equals(p1.Id, p2.Id) : false;
};
EDIT
To podejście jest podobne do tego w odpowiedzi Władimira Nesterowskiego, ale prostsze.Jest również podobny do tego w odpowiedzi Joela, ale pozwala na skomplikowaną logikę porównywania obejmujące wiele właściwości.
Jednakże, jeśli twoje obiekty mogą się różnić tylko o Id
, wtedy inny użytkownik dał poprawną odpowiedź, że wszystko, co musisz zrobić, to zastąpić domyślne implementacje GetHashCode()
i Equals()
w twojej klasie Person
, a następnie po prostu użyć out-of-the-box Distinct()
metody Linq, aby odfiltrować wszelkie duplikaty.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-22 18:03:53
Najlepszym sposobem, aby to zrobić, który będzie kompatybilny z innymi wersjami. NET jest nadpisanie Equals i GetHash, aby to obsłużyć (zobacz pytanie o przepełnienie stosu Ten kod zwraca różne wartości. Chcę jednak zwrócić mocno wpisaną kolekcję w przeciwieństwie do anonimowego typu), ale jeśli potrzebujesz czegoś, co jest ogólne w całym kodzie, rozwiązania w tym artykule są świetne.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:02:49
Override Equals (object obj) and GetHashCode () metody:
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();
}
}
A potem po prostu zadzwoń:
List<Person> distinctList = new[] { person1, person2, person3 }.Distinct().ToList();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-27 20:31:34
List<Person>lst=new List<Person>
var result1 = lst.OrderByDescending(a => a.ID).Select(a =>new Player {ID=a.ID,Name=a.Name} ).Distinct();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-16 10:42:56
Powinieneś być w stanie nadpisać Equals on person to actually do Equals on Person.id to powinno spowodować zachowanie, którego szukasz.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-28 20:49:52
Wypróbuj poniższy kod.
var Item = GetAll().GroupBy(x => x .Id).ToList();
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-16 07:24:26