TOC

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

LINQ:

Grouping data: the GroupBy() Method

Finora abbiamo lavorato soprattutto con elenchi di dati. Li abbiamo ordinati, limitati e modellati in nuovi oggetti, ma manca ancora un'operazione importante: Il raggruppamento dei dati. Quando si raggruppano i dati, si prende un elenco di qualcosa e lo si divide in diversi gruppi, in base a una o più proprietà. Immaginiamo di avere un'origine dati come questa:

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

Un semplice elenco di oggetti utente, ma potrebbe essere interessante raggruppare questi utenti in base, ad esempio, al loro paese di origine o alla loro età. Con LINQ, questo è molto semplice, anche se l'uso del metodo GroupBy() può essere un po' confuso all'inizio. Vediamo come funziona:

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

L'output risultante assomiglierà a questo:

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

L'esempio può sembrare un po' lungo, ma come si capirà presto, la maggior parte del lavoro consiste nella preparazione dell'origine dati. Ricordate che tutti i dati potrebbero provenire anche da un documento XML o da un database: è più facile fare una dimostrazione con una sorgente di dati a oggetti che si può usare così com'è.

La parte interessante è la creazione della variabile usersGroupedByCountry. La creiamo chiamando il metodo GroupBy() sulla nostra fonte di dati, fornendo il parametro in base al quale vogliamo raggruppare i dati. In questo caso, voglio che gli utenti siano raggruppati in base al loro paese di origine, quindi questa è la proprietà che fornisco al metodo GroupBy(). Il risultato è un oggetto con una proprietà Key, che contiene il valore della proprietà che abbiamo raggruppato (HomeCountry, in questo caso) e tutti gli oggetti che appartengono al gruppo. Nelle righe successive usiamo questo metodo per iterare sui gruppi appena creati e per ogni gruppo stampiamo la chiave (HomeCountry) e poi iteriamo e stampiamo tutti gli oggetti User del gruppo.

Chiavi di raggruppamento personalizzate

Come si può vedere, il raggruppamento in base a una proprietà esistente è facile, ma come ormai si sa, i metodi LINQ sono molto flessibili. È altrettanto semplice creare gruppi personalizzati, basati su qualsiasi cosa si desideri: un esempio potrebbe essere il seguente, in cui creiamo gruppi basati sulle prime due lettere del nome dell'utente:

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

È sufficiente richiamare il metodo Substring() sul nome, per ottenere le due prime lettere, e poi LINQ crea i gruppi di utenti in base ad esse. Il risultato sarà simile a questo:

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

Come si può vedere, siamo liberi di chiamare un metodo all'interno del metodo GroupBy(); in effetti, possiamo fare praticamente tutto quello che vogliamo, purché restituiamo qualcosa che LINQ possa usare per raggruppare gli elementi. Possiamo anche creare un metodo che restituisca una nuova informazione sull'elemento e poi usarla per creare un gruppo, come faremo nel prossimo esempio:

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

Si noti come sia stato implementato un metodo GetAgeGroup() nella classe User. Questo metodo restituisce una stringa che definisce il gruppo di età dell'utente e lo richiamiamo semplicemente nel metodo GroupBy() per usarlo come chiave di gruppo. Il risultato sarà simile a questo:

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

Ho scelto di implementare il metodo GetAgeGroup() nella classe User, perché potrebbe essere utile anche in altri luoghi, ma a volte serve solo un pezzo di logica veloce per creare i gruppi, da non riutilizzare altrove. In queste situazioni, si può fornire la logica direttamente al metodo GroupBy(), come espressione lambda, in questo modo:

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

Il risultato è ovviamente lo stesso!

Raggruppamento per chiave composita

Finora, le chiavi dei nostri gruppi sono state solo un singolo valore, ad esempio una proprietà o il risultato di una chiamata di metodo. Tuttavia, è possibile creare chiavi personalizzate che contengano più valori, chiamate chiavi composite. Un esempio di utilizzo potrebbe essere quello di raggruppare i nostri utenti in base al loro paese di origine e alla loro età, in questo modo:

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

}
    }
}

Notate la sintassi utilizzata nel metodo GroupBy(): invece di fornire una singola proprietà, creiamo un nuovo oggetto anonimo, che contiene le proprietà HomeCountry ed Age. LINQ creerà ora dei gruppi basati su queste due proprietà e collegherà l'oggetto anonimo alla proprietà Key del gruppo. Siamo liberi di usare entrambe le proprietà quando iteriamo sui gruppi, come si può vedere. Il risultato sarà simile a questo:

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]

Come sempre, in questo articolo abbiamo usato la sintassi di metodo per LINQ, ma permettimi di fornirti un esempio di confronto su come si potrebbe fare con la sintassi di query per LINQ:

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

Riepilogo

Come si può vedere dagli esempi di questo articolo, il metodo GroupBy() di LINQ è estremamente potente. Permette di utilizzare i dati in modi nuovi, con pochissimo codice. In precedenza, questo sarebbe stato molto complicato o avrebbe richiesto un database relazionale, ma con LINQ è possibile utilizzare qualsiasi fonte di dati e ottenere la stessa funzionalità di facile utilizzo.


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!