1. Introduzione
Nei progetti reali quasi uno sviluppatore su due spende buona parte del suo tempo a lavorare con le collezioni: filtra, conta, cerca, riordina, mette, prende — insomma, si comporta con loro quasi come davanti al frigorifero prima di dormire. Spesso bisogna estrarre, trasformare e aggregare dati — che siano liste di utenti, prodotti nel catalogo, righe di testo o qualsiasi altro array.
Quasi tutte le collezioni moderne in .NET supportano metodi funzionali — come Where, Select, Find, Any, All e altri. La loro forza è nell'universalità e nello stile conciso: passi semplicemente un "pezzetto di logica" come espressione lambda e la collezione prende vita, come se avessi avviato un nuovo motore.
LINQ (Language Integrated Query) non è solo zucchero sintattico, ma un mini-linguaggio dentro C# che permette di scrivere query sui dati come se usassi SQL o Excel. Solo che è meglio: direttamente nel codice, con autocomplete, tipi e debugger.
Tutta questa magia funziona grazie ai delegati — e scrivere ogni volta un metodo separato solo per filtrare un array è abbastanza noioso. Qui le espressioni lambda entrano in gioco come mini-funzioni inline, trasformando codice pesante in elegante e leggibile.
2. Espressioni lambda nei metodi standard delle collezioni
Le espressioni lambda brillano nei metodi standard delle collezioni basati su delegati, come Find, Exists, ForEach e molti altri.
Esempio: Ricerca per condizione
Supponiamo di avere una lista di prodotti:
using System;
using System.Collections.Generic;
// Nostra classe Product
public class Product
{
public string Name { get; set; }
public int Price { get; set; }
}
var products = new List<Product>
{
new Product { Name = "Kofe", Price = 100 },
new Product { Name = "Chaj", Price = 70 },
new Product { Name = "Moloko", Price = 80 }
};
// Troviamo il primo prodotto costoso (>90)
Product expensive = products.Find(p => p.Price > 90); // Usando la lambda!
Console.WriteLine(expensive?.Name); // => Kofe
Senza la lambda avresti dovuto scrivere un metodo separato o una funzione anonima nello stile vecchio. Così — una riga e il codice si legge come inglese: "Trova il prodotto dove il prezzo è maggiore di 90".
Esempio: Verifica presenza di un prodotto
bool hasCheap = products.Exists(p => p.Price < 75);
Console.WriteLine(hasCheap); // => True (perché "Chaj" è più economico di 75)
Esempio: Elaborare tutti gli elementi (ForEach)
A volte bisogna fare qualcosa per ogni elemento:
products.ForEach(p => Console.WriteLine($"{p.Name}: {p.Price} euro"));
Sulle analogie
In breve: le espressioni lambda nelle collezioni sono come il pulsante "migliora" in un editor di foto. Premi — e il risultato!
3. Espressioni lambda e LINQ: magia per le collezioni
LINQ non è solo comodità, è anche un ottimo ingresso nello stile funzionale. La maggior parte dei metodi LINQ accetta delegati — quindi il loro compagno ideale sono le espressioni lambda.
Filtraggio con Where
Riprendiamo la lista di prodotti. Ora selezioniamo solo i prodotti "economici":
using System.Linq;
var cheapProducts = products.Where(p => p.Price < 90);
foreach (var p in cheapProducts)
Console.WriteLine(p.Name); // Chaj, Moloko
Otteniamo una nuova collezione senza scrivere un ciclo manuale. Where prende una lambda-predicate (una funzione che restituisce true/false) e la applica a ogni elemento.
Ordinamento con OrderBy
Chi ama l'ordine — questo è per te:
var sorted = products.OrderBy(p => p.Price);
foreach (var p in sorted)
Console.WriteLine($"{p.Name}: {p.Price}");
// Chaj: 70
// Moloko: 80
// Kofe: 100
Mapping (Select) – proiezione dei dati
A volte non serve l'oggetto intero, ma solo una sua parte, ad esempio la lista dei nomi dei prodotti:
var names = products.Select(p => p.Name);
foreach (var name in names)
Console.WriteLine(name); // Kofe, Chaj, Moloko
Catene di LINQ
LINQ è comodo perché puoi "incatenare" le chiamate:
var namesOfCheap = products
.Where(p => p.Price < 90)
.OrderBy(p => p.Name)
.Select(p => p.Name.ToUpper());
foreach (var name in namesOfCheap)
Console.WriteLine(name); // MOLOKO, CHAJ
Sembra una catena di produzione: ogni metodo è una nuova fase di trasformazione.
Domanda: Perché le lambda sono migliori dei metodi normali per LINQ?
Prima di tutto, le lambda si scrivono nel punto in cui servono. Poi sono concise e leggibili. Infine, sono lo standard del C# moderno — tutti le usano, e chi non le usa spesso non supera i colloqui.
4. Esempio pratico
Durante il corso abbiamo scritto un'app didattica per lavorare con un piccolo catalogo di prodotti, utenti o ordini. Aggiungiamo metodi moderni di elaborazione delle collezioni.
Ricerca di un utente per nome
public class User
{
public string Username { get; set; }
public int Age { get; set; }
}
var users = new List<User>
{
new User{ Username = "Alice", Age = 21 },
new User{ Username = "Bob", Age = 26 },
new User{ Username = "Charlie", Age = 32 }
};
// Ricerca utente per nome
User found = users.FirstOrDefault(u => u.Username == "Bob");
Console.WriteLine(found?.Age); // 26
Filtraggio per età
var adults = users.Where(u => u.Age >= 18);
foreach (var u in adults)
Console.WriteLine(u.Username); // Alice, Bob, Charlie
Conteggio degli utenti
int count = users.Count(u => u.Age > 25);
Console.WriteLine(count); // 2 (Bob e Charlie)
Verificare se tutti gli utenti sono maggiorenni
bool allAdults = users.All(u => u.Age >= 18);
Console.WriteLine(allAdults); // True
C'è almeno un minorenne?
bool hasMinor = users.Any(u => u.Age < 18);
Console.WriteLine(hasMinor); // False
5. LINQ: Come funziona sotto
Quando scrivi, per esempio, Where(u => u.Age > 20), in realtà è più o meno lo stesso che creare un ciclo che itera tutti gli elementi e controlla la condizione per ciascuno. Solo che LINQ lo fa in modo invisibile e pulito, incapsulando il tuo predicato in un delegato.
Se non ci fossero le espressioni lambda, dovresti costruire cose del genere:
public static bool AgeMoreThan20(User u) => u.Age > 20;
var adultUsers = users.Where(AgeMoreThan20);
O addirittura metodi anonimi nello stile "old-school":
var adultUsers = users.Where(delegate(User u) { return u.Age > 20; });
Tutto questo è ingombrante e triste. Con la lambda è elegante e moderno.
6. Delegati e tipi standard: Func, Action, Predicate
Non solo LINQ ama le espressioni lambda. Molti metodi delle collezioni standard accettano delegati specializzati, per esempio:
- Predicate<T> — per metodi come Find, Exists, RemoveAll
- Func<T, TResult> — per i metodi LINQ, proiezioni, calcoli
- Action<T> — per metodi che fanno qualcosa con l'elemento ma non restituiscono nulla (ForEach)
Ecco come appare nella pratica:
// Predicate<T>
users.RemoveAll(u => u.Age < 30); // Rimosso chi ha meno di 30
// Func<T, TResult>
var names = users.Select(u => u.Username);
// Action<T>
users.ForEach(u => Console.WriteLine(u.Username));
7. Promemoria sui metodi delle collezioni con lambda
| Metodo | Cosa fa | Tipo di delegato | Esempio di lambda |
|---|---|---|---|
|
Filtra gli elementi | |
|
|
Proietta, trasforma | |
|
|
Ordinamento per chiave | |
|
|
Primo elemento che soddisfa la condizione | |
|
|
Esiste almeno un elemento che soddisfa la condizione | |
|
|
Tutti gli elementi soddisfano la condizione? | |
|
|
Numero di elementi che soddisfano la condizione | |
|
|
Fai qualcosa per ogni elemento | |
|
|
Rimuove tutto secondo il predicato | |
|
8. Errori comuni e caratteristiche
Uno degli errori più frequenti è dimenticare che LINQ non modifica la collezione originale, ma restituisce una nuova sequenza. Quindi, dopo var sorted = users.OrderBy(u => u.Age); la collezione users resterà nell'ordine originale! Questo può confondere: a volte sembra che sia già ordinata — ma in realtà no.
Un altro dettaglio: metodi come Where, Select e altri restituiscono oggetti di tipo IEnumerable<T>. Questa è una collezione "lazy" — l'elaborazione reale inizia quando effettivamente enumeri qualcosa (foreach, ToList() ecc.). Quindi, se vuoi materializzare il risultato, ricordati di chiamare ToList() o ToArray():
var sortedList = users.OrderBy(u => u.Age).ToList();
Ricordiamo anche: se la lambda cattura variabili esterne (closures), quelle variabili continueranno "a vivere" in memoria finché vive il riferimento alla lambda. Non è terribile, ma se usi la lambda dentro un oggetto di lunga vita e hai catturato un "array enorme", quell'array resterà in memoria insieme alla lambda.
E ancora: usa nomi di parametri e variabili descrittivi — questo migliora molto la leggibilità, specialmente se hai più livelli di lambda annidate.
GO TO FULL VERSION