1. Introduzione
Potresti non saperlo, ma una delle domande preferite ai colloqui è “Cosa sai delle espressioni lambda?”. Perché la chiedono? Perché è una feature molto comoda e "alla moda" in C#, che permette di scrivere meno codice e renderlo più espressivo. Se i metodi anonimi sono come scrivere una lettera a mano, le espressioni lambda sono come mandare un messaggio su un messenger: veloce, compatto e a volte anche con emoji (beh, quasi).
Le espressioni lambda sono arrivate in C# con la versione 3.0 e da allora si sono diffuse ovunque: in LINQ, negli handler di eventi, nei thread, nelle collezioni e persino nelle librerie di AI su .NET.
Un'espressione lambda è un modo per dichiarare un metodo direttamente nel corpo del codice, "sul posto", senza nome, super conciso e chiaro. La sintassi base è sempre così:
(parametri) => espressione_o_blocco_di_codice
Questo "segno" => si chiama “operatore lambda” o “freccia”.
Una piccola analogia
Ricorda come scrivi una ricetta: “Prendere una mela, tagliarla, metterla nella ciotola”.
In C# sarebbe:
(mela) => tagliare(mela)
2. Trasformiamo un metodo anonimo in una lambda
Supponiamo di avere un delegato:
delegate int SquareDelegate(int x);
Il metodo anonimo apparirebbe così:
SquareDelegate sq = delegate(int x) {
return x * x;
};
E ora lo stesso usando un'espressione lambda:
SquareDelegate sq = (int x) => { return x * x; };
Ma C# può inferire i tipi, e possiamo accorciare ancora:
SquareDelegate sq = x => x * x;
Che compattezza fantastica!
Confronto con un metodo "normale" e una funzione anonima
| Modalità | Dichiarazione | Dove si può usare | Svantaggi |
|---|---|---|---|
| Metodo nominato | In una classe | Ovunque | Serve un nome, più codice |
| Metodo anonimo | Nel codice | Solo con delegati | Sintassi più verbosa |
| Espressione lambda | Nel codice | Ovunque con delegati | Talvolta il tipo non è ovvio |
3. Sintassi dell'espressione lambda: varianti
Parametri
Senza parametri:
Action hello = () => Console.WriteLine("Ciao, mondo!");
Un parametro (puoi omettere le parentesi):
Func<int, int> inc = x => x + 1;
Più parametri (servono le parentesi):
Func<int, int, int> sum = (a, b) => a + b;
Corpo della lambda
Una singola espressione — senza parentesi graffe e senza return:
x => x * x
Blocco di codice — parentesi graffe, serve il return:
(x, y) =>
{
int z = x + y;
return z * z;
}
Tipi dei parametri
Di solito non serve specificare i tipi — il compilatore li deduce. Ma se vuoi, puoi:
(x, y) => x + y // Il compilatore inferisce i tipi.
(int x, int y) => x + y // Puoi specificarli esplicitamente.
4. Sfumature e esempi più realistici
Uso con le collezioni
Ricorda la nostra mini-app didattica (per esempio, una lista di utenti)? Supponiamo di avere un array di numeri:
int[] numbers = { 1, 2, 3, 4, 5 };
Vogliamo selezionare solo i numeri pari:
var evenNumbers = numbers.Where(n => n % 2 == 0);
Qui Where è un metodo di estensione LINQ, e la condizione è il nostro filtro lambda.
Passare come parametro a un metodo
Supponiamo di avere un metodo delegato:
public delegate bool Filter(int number);
public static int[] FilterNumbers(int[] data, Filter predicate)
{
var result = new List<int>();
foreach (var n in data)
if (predicate(n))
result.Add(n);
return result.ToArray();
}
Ora passiamo un'espressione lambda:
int[] evens = FilterNumbers(numbers, n => n % 2 == 0);
Lambda come gestore di eventi
button.Click += (sender, args) => Console.WriteLine("Pulsante premuto!");
5. Lambda e i delegati generici standard
È difficile immaginare le lambda senza Func<>, Action<> e Predicate<>. Sono tipi speciali di delegati:
- Action — non restituisce nulla.
- Func — restituisce un valore.
- Predicate — restituisce bool, di solito per filtrare.
Esempio con Action:
Action<string> log = message => Console.WriteLine(message);
log("Questo è un messaggio via lambda!");
Esempio con Func:
Func<int, int, int> multiply = (a, b) => a * b;
int product = multiply(3, 5); // 15
Esempio con Predicate:
Predicate<int> isNegative = n => n < 0;
bool test = isNegative(-7); // true
6. Lambda con più espressioni: quando serve il blocco
Se la lambda deve eseguire più operazioni, usa le parentesi graffe e il return esplicito:
Func<int, int, string> describeSum = (a, b) =>
{
int sum = a + b;
return $"Somma: {sum}";
};
Senza return il compilatore si lamenterà (ma non spiegherà tanto bene il perché — dirà semplicemente che "non tutti i percorsi ritornano un valore").
7. Restituire void: Action
Quando la lambda non restituisce nulla, usa Action:
Action greet = () => Console.WriteLine("Sorridi — il codice compila!");
La lambda può contenere quante istruzioni vuoi:
Action<int> printSquare = x =>
{
int sq = x * x;
Console.WriteLine($"Il quadrato di {x} è {sq}");
};
8. Limitazioni ed errori tipici
A volte vorresti scrivere la lambda in modo molto libero, ma il compilatore non sempre è d'accordo.
Non puoi dichiarare una variabile con lo stesso nome di una catturata dall'esterno.
Fai attenzione ai tipi: se la firma del delegato e quella della lambda non coincidono, ci sarà un errore.
Il ritorno di un valore è obbligatorio se il delegato richiede un valore di ritorno.
In lambda complesse non dimenticare le parentesi graffe. Una singola espressione — senza graffe e senza return. Più istruzioni — graffe e return.
GO TO FULL VERSION