consulta linq para retornar valores de campo distintos de uma lista de objetos

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

Suponha que haja uma lista com 100 elementos de obj, mas apenas 10 typeIDs exclusivos.
É possível escrever uma consulta LINQ para retornar os 10 ints exclusivos da lista de objs?

patrick
fonte
Esta questão é tão fácil e não cabe para generosidade.
Thinker

Respostas:

157
objList.Select(o=>o.typeId).Distinct()
Arsen Mkrtchyan
fonte
2
A segunda forma parece usar uma sobrecarga de Distinct que não existe até onde eu sei.
Jon Skeet
Ops, removeu o segundo, obrigado @Jon Skeet, isso pode ser feito com IEqualityComparer
Arsen Mkrtchyan
3
Como Jon Skeet mencionou, isso retornará apenas as propriedades especificadas no Select.
Peet vd Westhuizen,
59

Supondo que você deseja o objeto completo, mas deseja apenas lidar com a distinção por typeID, não há nada integrado ao LINQ para tornar isso fácil. (Se você deseja apenas os typeIDvalores, é fácil - projete com base nisso Selecte use a Distinctchamada normal .)

Em MoreLINQ , temos o DistinctByoperador que você pode usar:

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

No entanto, isso só funciona para LINQ to Objects.

Você pode usar um agrupamento ou pesquisa, é apenas um pouco irritante e ineficiente:

var distinct = list.GroupBy(x => x.typeID, (key, group) => group.First());
Jon Skeet
fonte
Para que DistinctBy apareça, você precisa adicionar o namespace Microsoft.Ajax.Utilities
Shiljo Paulson
1
@Shil: Não, eu estava escrevendo sobre o DistinctBy em MoreLINQ. Nada a ver com Microsoft.Ajax.Utilities.
Jon Skeet
Agora eu posso ver que há uma sobrecarga de Distinct no LINQ que leva um IEqualityComparer como parâmetro e retorna uma lista de objetos distintos dependendo da implementação de métodos em IEqualityComparer
Dipendu Paul
1
@DipenduPaul: Sim, mas isso ainda significa criar um comparador de igualdade para uma determinada propriedade, o que é irritante e torna-a mais difícil de ler. Se você pode usar a dependência MoreLINQ, acho que é mais limpo.
Jon Skeet
27

Se quiser apenas usar o Linq puro, você pode usar groupby:

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

Se você quiser que um método seja usado em todo o aplicativo, semelhante ao que MoreLinq faz:

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

Usando este método para encontrar os valores distintos usando apenas a propriedade Id, você pode usar:

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

você pode usar várias propriedades:

var query = objs.DistinctBy(p => new { p.TypeId, p.Name });
JulioCT
fonte
Obrigado pela parte de várias propriedades !
Nae
7

Claro, use Enumerable.Distinct.

Dada uma coleção de obj(por exemplo foo), você faria algo assim:

var distinctTypeIDs = foo.Select(x => x.typeID).Distinct();
Rosquinha
fonte
5

Acho que é isso que você está procurando:

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

Semelhante a este Retornando um IQueryable distinto com LINQ?

Calibrar
fonte
.Firstestá bem, já que você não teria o grupo se não houvesse algo nele.
mqp de
1
Você pode usar uma sobrecarga alternativa de GroupBypara tornar isso mais simples também - veja minha resposta para um exemplo.
Jon Skeet
3

Se quiser apenas usar o Linq, você pode substituir os métodos Equals e GetHashCode .

Classe de produto :

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

Método Principal :

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
fonte
1

Eu queria vincular um dado específico ao menu suspenso e ele deveria ser distinto. Eu fiz o seguinte:

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

Veja se ajuda

Charmy Vora
fonte