zapytanie linq, aby zwrócić różne wartości pól z listy obiektów

89
class obj
{
    int typeId; //10 types  0-9 
    string uniqueString; //this is unique
}

Załóżmy, że istnieje lista zawierająca 100 elementów obj, ale tylko 10 unikalnych identyfikatorów typów.
Czy można napisać zapytanie LINQ, które zwróci 10 unikatowych liczb całkowitych z listy obiektów?

patrick
źródło
To pytanie jest tak łatwe i nie nadaje się do nagrody.
Thinker

Odpowiedzi:

155
objList.Select(o=>o.typeId).Distinct()
Arsen Mkrtchyan
źródło
2
Druga forma wydaje się używać przeciążenia Distinct, które, o ile wiem, nie istnieje.
Jon Skeet
Ups, usunąłem drugą, dzięki @Jon Skeet, można to zrobić z IEqualityComparer
Arsen Mkrtchyan
3
Jak wspomniał Jon Skeet, zwróci to tylko właściwości określone w Select.
Peet vd Westhuizen
58

Zakładając, że chcesz mieć pełny obiekt, ale chcesz radzić sobie tylko z odrębnością typeID, nie ma nic wbudowanego w LINQ, aby to ułatwić. (Jeśli tylko chcesz, aby typeIDwartości, to proste - projekt, który z Select, a następnie użyć normalnej Distinctrozmowy).

W MoreLINQ mamy DistinctByoperator, którego możesz użyć:

var distinct = list.DistinctBy(x => x.typeID);

Działa to tylko w przypadku LINQ to Objects.

Możesz użyć grupowania lub wyszukiwania, jest to trochę denerwujące i nieefektywne:

var distinct = list.GroupBy(x => x.typeID, (key, group) => group.First());
Jon Skeet
źródło
Aby mieć DistinctBy pojawiać trzeba dodać Microsoft.Ajax.Utilities przestrzeni nazw
Shiljo Paulsona
1
@Shil: Nie, pisałem o DistinctBy w MoreLINQ. Nie ma to nic wspólnego z Microsoft.Ajax.Utilities.
Jon Skeet,
Teraz widzę, że w LINQ występuje przeciążenie Distinct, które przyjmuje IEqualityComparer jako parametr i zwraca listę różnych obiektów w zależności od implementacji metod w IEqualityComparer
Dipendu Paul
1
@DipenduPaul: Tak, ale to nadal oznacza tworzenie porównywarki równości dla danej właściwości, co jest denerwujące i utrudnia czytanie. Jeśli możesz wziąć zależność MoreLINQ, myślę, że jest to czystsze.
Jon Skeet,
22

Jeśli chcesz tylko użyć czystego Linq, możesz użyć groupby:

List<obj> distinct =
  objs.GroupBy(car => car.typeID).Select(g => g.First()).ToList();

Jeśli chcesz, aby metoda była używana w całej aplikacji, podobnie jak robi to MoreLinq :

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.Contains(keySelector(element)))
        {
            seenKeys.Add(keySelector(element));
            yield return element;
        }
    }
}

Korzystając z tej metody, aby znaleźć różne wartości przy użyciu tylko właściwości Id, możesz użyć:

var query = objs.DistinctBy(p => p.TypeId);

możesz użyć wielu właściwości:

var query = objs.DistinctBy(p => new { p.TypeId, p.Name });
JulioCT
źródło
Dzięki za część dotyczącą wielu właściwości !
Nae
7

Jasne, użyj Enumerable.Distinct.

Biorąc pod uwagę kolekcję obj(np. foo), Zrobiłbyś coś takiego:

var distinctTypeIDs = foo.Select(x => x.typeID).Distinct();
Pączek
źródło
5

Myślę, że tego właśnie szukasz:

    var objs= (from c in List_Objects 
orderby c.TypeID  select c).GroupBy(g=>g.TypeID).Select(x=>x.FirstOrDefault());      

Podobnie do tego zwracania wyróżniającego się IQueryable z LINQ?

Zastaw
źródło
.Firstjest w porządku, ponieważ nie miałbyś grupy, gdyby czegoś w niej nie było.
mqp
1
Możesz użyć alternatywnego przeciążenia, GroupByaby to uprościć - zobacz przykład w mojej odpowiedzi.
Jon Skeet
3

Jeśli chcesz tylko użyć Linq, możesz przesłonić metody Equals i GetHashCode .

Klasa produktu :

public class Product
{
    public string ProductName { get; set; }
    public int Id { get; set; }


    public override bool Equals(object obj)
    {
        if (!(obj is Product))
        {
            return false;
        }

        var other = (Product)obj;
        return Id == other.Id;
    }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }
}

Główna metoda:

static void Main(string[] args)
    {

        var products = new List<Product>
        {
            new Product{ ProductName="Product 1",Id = 1},
            new Product{ ProductName="Product 2",Id = 2},
            new Product{ ProductName="Product 4",Id = 5},
            new Product{ ProductName="Product 3",Id = 3},
            new Product{ ProductName="Product 4",Id = 4},
            new Product{ ProductName="Product 6",Id = 4},
            new Product{ ProductName="Product 6",Id = 4},
        };

        var itemsDistinctByProductName = products.Distinct().ToList();

        foreach (var product in itemsDistinctByProductName)
        {
            Console.WriteLine($"Product Id : {product.Id} ProductName : {product.ProductName} ");
        }

        Console.ReadKey();
    }
Reza Jenabi
źródło
1

Chciałem powiązać określone dane z menu rozwijanym i powinno być odrębne. Zrobiłem co następuje:

List<ClassDetails> classDetails;
List<string> classDetailsData = classDetails.Select(dt => dt.Data).Distinct.ToList();
ddlData.DataSource = classDetailsData;
ddlData.Databind();

Zobacz, czy to pomaga

Charmy Vora
źródło