TOC

This article is currently in the process of being translated into French (~38% done).

LINQ:

Grouper les données: la méthode GroupBy()

Pour l'instant, nous avons travaillé avec la plupart des listes de données. Nous les avons triées, limité et formé dans des nouveaux objets, mais, une des opérations les plus importantes qui n'est pas encore vue est: Grouper les données. Quand vous groupez les données, vous prenez une liste de quelque chose et ensuite, vous la divisez entre plusieurs groupes basé sur une ou plusieurs propriétés. Imaginez que nous avons une source de données comme celle-là:

var users = new List<User>()
{
    new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
    new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
    new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
    new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
    new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
};

Une simple liste d'utilisateurs en objet, mais cela devient intéressant de groupé ces utilisateurs comme par exemple: leur pays d'origine ou leur âge. avec LINQ, c'est très facile, même si l'utilisation de la méthode GroupBy() peut porter à confusion dans le commencement. Regardons comment ça marche:

using System;    
using System.Collections.Generic;    
using System.Linq;    

namespace LinqGroup    
{    
    class Program    
    {    
static void Main(string[] args)    
{    
    var users = new List<User>()    
    {    
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },    
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },    
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },    
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },    
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },    
    };    
    var usersGroupedByCountry = users.GroupBy(user => user.HomeCountry);    
    foreach(var group in usersGroupedByCountry)    
    {    
Console.WriteLine("Users from " + group.Key + ":");    
foreach(var user in group)    
Console.WriteLine("* " + user.Name);
    }    
}    

public class User    
{    
    public string Name { get; set; }    

    public int Age { get; set; }    

    public string HomeCountry { get; set; }    
}    
    }    
}

Le résultat devrait ressembler à ça:

Users from USA:
* John Doe
* Jane Doe
* James Doe
Users from Germany:
* Joe Doe
* Jenna Doe

L'exemple peut sembler un peu long, mais vous vous rendez vite compte que la plupart du code est de préparer la source de donnée. Rappellez-vous que toute les données peuvent venir dans un document XML ou une base de données. C'est très facile de montrer avec un objet comme source de données que vous pouvez utiliser tel quel.

La partie intéressante est quand nous créons la variable userGroupedByContry. nous l'avons créée pour appelez la méthode GroupBy() pour notre source de données en fournissant le paramètre que nous voulons pour le groupe de données. Dans ce cas, je veux que les utilisateurs soient groupés par leur pays d'origine. C'est cette propriété que je vais fournir à la méthode GroupBy(). Le résultat est en object avec la propriété Key qui détient la valeur de la propriété que nous avons groupé (HomeContry, dans ce cas), ainsi que tout les objets appartenant au même groupe. Nous utilisons ça dans la prochaine ligne pour parcourir et imprimer dans la console tout les utilisateurs de l'objet User depuis le groupe.

Clés des groupes personnalisables

As you can see, grouping by an existing property is easy-peasy, but as you might have come to know by now, the LINQ methods are very flexible. It's just as simple to create your own, custom groups, based on whatever you like - an example of that could be the following, where we create groups based on the first two letters of the user's name:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };
    var usersGroupedByFirstLetters = users.GroupBy(user => user.Name.Substring(0, 2));
    foreach(var group in usersGroupedByFirstLetters)
    {
Console.WriteLine("Users starting with " + group.Key + ":");
foreach(var user in group)
    Console.WriteLine("* " + user.Name);
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }
}
    }
}

We simply call the Substring() method on the name, to get the two first letters, and then LINQ creates the groups of users based on it. The result will look something like this:

Users starting with Jo:
* John Doe
* Joe Doe
Users starting with Ja:
* Jane Doe
* James Doe
Users starting with Je:
* Jenna Doe

So as you can see, we are free to call a method inside the GroupBy() method - in fact, we can do pretty much whatever we want in there, as long as we return something that LINQ can use to group the items. We can even create a method which returns a new piece of information about the item and then use it to create a group, as we do in the next example:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };
    var usersGroupedByAgeGroup = users.GroupBy(user => user.GetAgeGroup());
    foreach(var group in usersGroupedByAgeGroup)
    {
Console.WriteLine(group.Key + ":");
foreach(var user in group)
    Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }

    public string GetAgeGroup()
    {
if (this.Age < 13)
    return "Children";
if (this.Age < 20)
    return "Teenagers";
return "Adults";
    }
}
    }
}

Notice how I have implemented a GetAgeGroup() method on the User class. It returns a string that defines the age group of the user and we simply call it in the GroupBy() method to use it as a group key. The result will look like this:

Adults:
* John Doe [42 years]
* Jane Doe [38 years]
Teenagers:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Children:
* James Doe [8 years]

I choose to implement the GetAgeGroup() method on the User class, because it might be useful in other places, but sometimes you just need a quick piece of logic to create the groups, not to be re-used elsewhere. In those situations, you are free to supply the logic directly to the GroupBy() method, as a lambda expression, like this:

var usersGroupedByAgeGroup = users.GroupBy(user =>
    {
if (user.Age < 13)
    return "Children";
if (user.Age < 20)
    return "Teenagers";
return "Adults";
    });

Le résultat sera bien sûr le même!

Grouper par une clé composite

So far, the keys of our groups have just been a single value, e.g. a property or the result of a method call. However, you are free to create your own keys which contain several values - these are called composite keys. A usage example could be if we wanted to group our users based on both their home country and their age, like this:

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqGroup2
{
    class Program
    {
static void Main(string[] args)
{
    var users = new List<User>()
    {
new User { Name = "John Doe", Age = 42, HomeCountry = "USA" },
new User { Name = "Jane Doe", Age = 38, HomeCountry = "USA" },
new User { Name = "Joe Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "Jenna Doe", Age = 19, HomeCountry = "Germany" },
new User { Name = "James Doe", Age = 8, HomeCountry = "USA" },
    };

    var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
    foreach(var group in usersGroupedByCountryAndAge)
    {
Console.WriteLine("Users from " + group.Key.HomeCountry + " at the age of " + group.Key.Age + ":");
foreach (var user in group)
    Console.WriteLine("* " + user.Name + " [" + user.Age + " years]");
    }
}

public class User
{
    public string Name { get; set; }

    public int Age { get; set; }

    public string HomeCountry { get; set; }

}
    }
}

Notice the syntax we use in the GroupBy() method - instead of supplying a single property, we create a new anonymous object, which contains the HomeCountry and the Age properties. LINQ will now create groups based on these two properties and attach the anonymous object to the Key property of the group. We are free to use both properties when we iterate over the groups, as you can see. The result will look something like this:

Users from USA at the age of 42:
* John Doe [42 years]
Users from USA at the age of 38:
* Jane Doe [38 years]
Users from Germany at the age of 19:
* Joe Doe [19 years]
* Jenna Doe [19 years]
Users from USA at the age of 8:
* James Doe [8 years]

As always, we have used the LINQ Method syntax through this article, but allow me to supply you with a comparison example on how it could be done with the LINQ Query syntax:

// Method syntax
var usersGroupedByCountryAndAge = users.GroupBy(user => new { user.HomeCountry, user.Age });
// Query syntax
var usersGroupedByCountryAndAgeQ = from user in users group user by new { user.HomeCountry, user.Age } into userGroup select userGroup;

En résumé

As you can probably see from the examples in this article, the GroupBy() method of LINQ is extremely powerful. It really allows you to use your data in new ways, with very little code. Previously this would either be very cumbersome or require a relational database, but with LINQ, you can use whatever data source you'd like and still get the same, easy-to-use functionality.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!