Selecione vários campos da lista no Linq

128

No ASP.NET C #, tenho uma estrutura:

public struct Data
{
    public int item1;
    public int item2;
    public int category_id;
    public string category_name;
}

e eu tenho uma lista deles. Quero selecionar category_ide category_name, executando um DISTINCTe, finalmente, um ORDERBYdiante category_name.

Aqui está o que eu tenho agora:

List<Data> listObject = getData();
string[] catNames = listObject
                    .Select(i=> i.category_name)
                    .Distinct()
                    .OrderByDescending(s => s)
                    .ToArray();

Obviamente, isso apenas recebe o nome da categoria. Minha pergunta é: como obtenho vários campos e em qual estrutura de dados armazenarei isso (não a string[])?

EDITAR

O uso de uma lista de estruturas não é definido como pedra. Se seria aconselhável alterar minha estrutura de dados de apoio para facilitar as seleções (eu escreverei muitas delas), então eu ficaria feliz em fazer recomendações.

Chet
fonte
10
Embora não seja relacionado ao lado do LINQ, eu recomendo fortemente que você não use estruturas mutáveis ​​ou campos públicos. Pessoalmente, raramente crio estruturas em primeiro lugar, mas estruturas mutáveis ​​estão apenas pedindo problemas.
Jon Skeet
@ Jon Skeet Obrigado. Vou convertê-lo para uma aula regular com membros particulares.
Chet
1
@ Midid: Estruturas mutáveis ​​causam todos os tipos de problemas, pois não se comportam como as pessoas esperam. E os campos públicos dão uma completa falta de encapsulamento.
Jon Skeet
@Jon Skeet. Você pode ser mais específico com as armadilhas das estruturas mutáveis ​​ou me indicar uma leitura.
Midhat
2
@ Midid: Dê uma olhada em stackoverflow.com/questions/441309/why-are-mutable-structs-evil para um ponto de partida.
Jon Skeet

Respostas:

228

Tipos anônimos permitem selecionar campos arbitrários em estruturas de dados que são fortemente digitadas posteriormente no seu código:

var cats = listObject
    .Select(i => new { i.category_id, i.category_name })
    .Distinct()
    .OrderByDescending(i => i.category_name)
    .ToArray();

Como você (aparentemente) precisa armazená-lo para uso posterior, você pode usar o operador GroupBy:

Data[] cats = listObject
    .GroupBy(i => new { i.category_id, i.category_name })
    .OrderByDescending(g => g.Key.category_name)
    .Select(g => g.First())
    .ToArray();
Jason
fonte
Eu quero usar distintas em 1 coluna e recuperar várias colunas. SO, como posso fazer isso?
Kishan Gajjar #
1
Eu nunca pensei Selectem um novo tipo. No meu caso, selecionei um novo KeyValuePair.
Cjbarth
26
var selectedCategories =
    from value in
        (from data in listObject
        orderby data.category_name descending
        select new { ID = data.category_id, Name = data.category_name })
    group value by value.Name into g
    select g.First();

foreach (var category in selectedCategories) Console.WriteLine(category);

Edit : Tornou mais LINQ-ey!

IRBMe
fonte
Haha, obrigado, bom, agradeço a ajuda. Aparentemente, essa não era a pergunta mais difícil do mundo.
Chet
@IRBMe Com licença, pequena pergunta. O que é exatamente 'valor'?
Fernando S. Kroes
23

Você pode usar um tipo anônimo:

.Select(i => new { i.name, i.category_name })

O compilador irá gerar o código para uma classe com namee category_namepropriedades e retorna instâncias dessa classe. Você também pode especificar manualmente os nomes das propriedades:

i => new { Id = i.category_id, Name = i.category_name }

Você pode ter um número arbitrário de propriedades.

Mehrdad Afshari
fonte
6

Você pode selecionar vários campos usando linq Select, como mostrado acima em vários exemplos, isso retornará como um Tipo Anônimo. Se você deseja evitar esse tipo anônimo, aqui está o truque simples.

var items = listObject.Select(f => new List<int>() { f.Item1, f.Item2 }).SelectMany(item => item).Distinct();

Eu acho que isso resolve seu problema

AR M
fonte
5

Essa é uma tarefa para a qual tipos anônimos são muito adequados. Você pode retornar objetos de um tipo que é criado automaticamente pelo compilador, deduzido do uso.

A sintaxe é desta forma:

new { Property1 = value1, Property2 = value2, ... }

Para o seu caso, tente algo como o seguinte:

var listObject = getData();
var catNames = listObject.Select(i =>
    new { CatName = i.category_name, Item1 = i.item1, Item2 = i.item2 })
    .Distinct().OrderByDescending(s => s).ToArray();
Noldorin
fonte
4
var result = listObject.Select( i => new{ i.category_name, i.category_id } )

Isso usa tipos anônimos, portanto, você deve usar a palavra-chave var, pois o tipo resultante da expressão não é conhecido antecipadamente.

Paul van Brenk
fonte
3
(from i in list
 select new { i.category_id, i.category_name })
 .Distinct()
 .OrderBy(i => i.category_name);
Joe Chung
fonte
3

Você pode torná-lo um KeyValuePair, para que ele retorne um "IEnumerable<KeyValuePair<string, string>>"

Então, será assim:

.Select(i => new KeyValuePair<string, string>(i.category_id, i.category_name )).Distinct();
Vencedor
fonte
Isso adiciona complexidade desnecessária, sem um benefício
Matze
2
public class Student
{
    public string Name { set; get; }
    public int ID { set; get; }
}

class Program
{
  static void Main(string[] args)
    {
        Student[] students =
        {
        new Student { Name="zoyeb" , ID=1},
        new Student { Name="Siddiq" , ID=2},
        new Student { Name="sam" , ID=3},
        new Student { Name="james" , ID=4},
        new Student { Name="sonia" , ID=5}
        };

        var studentCollection = from s in students select new { s.ID , s.Name};

        foreach (var student in studentCollection)
        {
            Console.WriteLine(student.Name);
            Console.WriteLine(student.ID);
        }
    }
}
Zoyeb Shaikh
fonte