Sélectionnez plusieurs champs dans la liste dans Linq

128

Dans ASP.NET C #, j'ai une structure:

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

et j'en ai une liste. Je veux choisir category_idet category_name, en cours d' exécution d' un DISTINCTet enfin un ORDERBYsur category_name.

Voici ce que j'ai maintenant:

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

Cela n'a évidemment que le nom de la catégorie. Ma question est la suivante: comment puis-je obtenir plusieurs champs et dans quelle structure de données vais-je les stocker (et non dans une string[])?

ÉDITER

L'utilisation d'une liste de structures n'est pas gravée dans la pierre. S'il serait souhaitable de modifier la structure de mes données de sauvegarde pour faciliter les sélections (j'en écrirai beaucoup), je serais ravi de prendre des recommandations.

Chet
la source
10
Bien que cela ne soit pas lié au côté LINQ, je vous déconseille fortement d'utiliser des structures mutables ou des champs publics. Personnellement, je crée rarement des structures en premier lieu, mais les structures mutables ne demandent que des problèmes.
Jon Skeet le
@Jon Skeet Merci. Je vais le convertir en un cours régulier avec des membres privés.
Chet
1
@Midhat: Les structures mutables causent toutes sortes de problèmes, car elles ne se comportent pas comme les gens l'attendent. Et les champs publics donnent un manque total d'encapsulation.
Jon Skeet
@Jon Skeet. Pouvez-vous être plus précis avec les pièges des structures mutables, ou indiquez-moi une lecture.
Midhat
2
@Midhat: jetez un œil à stackoverflow.com/questions/441309/why-are-mutable-structs-evil pour un point de départ.
Jon Skeet

Réponses:

228

Les types anonymes vous permettent de sélectionner des champs arbitraires dans des structures de données qui sont fortement typées plus tard dans votre code:

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

Puisque vous devez (apparemment) le stocker pour une utilisation ultérieure, vous pouvez utiliser l'opérateur 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
la source
Je veux utiliser distinct sur 1 colonne et récupérer plusieurs colonnes, alors comment puis-je le faire?
Kishan Gajjar
1
Je n'ai jamais pensé à Selectun nouveau type. Dans mon cas, j'ai sélectionné dans un nouveau 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 : fait plus LINQ-ey!

IRBMe
la source
Haha, merci, eh bien, j'apprécie l'aide. Apparemment, ce n'était pas la question la plus difficile au monde.
Chet
@IRBMe Excusez-moi, petite question. Quelle est exactement la «valeur»?
Fernando S. Kroes
23

Vous pouvez utiliser un type anonyme:

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

Le compilateur va générer le code pour une classe nameet category_nameinstances de propriétés et rendements de cette catégorie. Vous pouvez également spécifier manuellement les noms de propriétés:

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

Vous pouvez avoir un nombre arbitraire de propriétés.

Mehrdad Afshari
la source
6

Vous pouvez sélectionner plusieurs champs à l'aide de linq Select comme indiqué ci-dessus dans divers exemples, cela retournera comme un type anonyme. Si vous voulez éviter ce type anonyme, voici une astuce simple.

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

Je pense que cela résout ton problème

BRAS
la source
5

C'est une tâche pour laquelle les types anonymes sont très bien adaptés. Vous pouvez renvoyer des objets d'un type créé automatiquement par le compilateur, déduit de l'utilisation.

La syntaxe est de cette forme:

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

Pour votre cas, essayez quelque chose comme ce qui suit:

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
la source
4
var result = listObject.Select( i => new{ i.category_name, i.category_id } )

Cela utilise des types anonymes, vous devez donc utiliser le mot-clé var, car le type résultant de l'expression n'est pas connu à l'avance.

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

Vous pouvez en faire un KeyValuePair, il renverra donc un "IEnumerable<KeyValuePair<string, string>>"

Donc, ce sera comme ça:

.Select(i => new KeyValuePair<string, string>(i.category_id, i.category_name )).Distinct();
Victor
la source
Cela ajoute une complexité inutile sans avantage
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
la source