CodeGym /Corsi /C# SELF /Espressioni lambda con Fun...

Espressioni lambda con Func, Action, Predicate

C# SELF
Livello 50 , Lezione 1
Disponibile

1. Introduzione

Sai cosa è interessante? L'espressione lambda in sé è come una ricetta senza pentola. Descrive cosa fare, ma ha bisogno di un "contenitore" per vivere nel codice. In C# per questo ci sono i delegati generici pronti: Func, Action e Predicate.

Immaginali come stampi pronti per le tue lambda — prendi quello giusto e versa la tua logica. Niente bici fatte in casa o tipi di delegati personalizzati, quando hai già tutto a portata di mano.

Un po' di storia

All'inizio, quando serviva passare una funzione come parametro, bisognava dichiarare un tipo di delegato personalizzato. Era lungo, noioso e sembrava così:

delegate int Calculate(int x, int y);

Calculate adder = (a, b) => a + b;

Quando in C# sono arrivati Func, Action e Predicate, si poteva dimenticare la dichiarazione manuale dei delegati nel 90% dei casi. Ora è molto più semplice e universale:

Func<int, int, int> adder = (a, b) => a + b;

2. Func<T, TResult> — funzione con ritorno

Sintassi e scopo

Func è un delegato generico (generic delegate) che prende da 0 a 16 parametri e restituisce un valore.

Func<int, int, int> sum = (x, y) => x + y;

Func<tipo1, tipo2, ..., tipoN, TResult> — tutti i parametri fino all'ultimo sono argomenti, l'ultimo è il tipo restituito.

Esempi

1. Somma di due numeri

Func<int, int, int> sum = (x, y) => x + y;
Console.WriteLine(sum(3, 5)); // 8

2. Elevare al quadrato

Func<int, int> square = x => x * x;
Console.WriteLine(square(4)); // 16

3. Senza parametri

Func<string> greet = () => "Privet, lambda!";
Console.WriteLine(greet());

Schema visivo

Firma Esempio Descrizione
Func<int, int>
x => x * 2
Accetta int, restituisce int
Func<int, int, int>
(a, b) => a + b
Due int, restituisce int
Func<string>
() => "hi"
Non prende nulla, restituisce una stringa

Come appare nella tua applicazione

Supponiamo che nella nostra mini-applicazione (il finto "Rubrica utenti"), abbiamo una lista di numeri — vogliamo applicare diverse operazioni. Per esempio elevare al quadrato o sommare con un numero fisso:

List<int> numbers = new() { 1, 2, 3, 4, 5 };

Func<int, int> square = x => x * x;
var squares = numbers.Select(square);
Console.WriteLine(string.Join(", ", squares)); // 1, 4, 9, 16, 25

3. Action<T> — azione senza ritorno

Action è il delegato generico per metodi che fanno qualcosa (per esempio stampano a schermo), ma non restituiscono nulla.

Può accettare da 0 a 16 parametri, ma restituisce sempre void.

Esempi

1. Stampare a schermo

Action<string> print = text => Console.WriteLine("Dannye: " + text);
print("Privet, mir!");

2. Azione senza parametri

Action greet = () => Console.WriteLine("Dobro pozhalovat'!");
greet();

3. Azione con più parametri

Action<int, int> showSum = (a, b) => Console.WriteLine($"Summa: {a + b}");
showSum(2, 3); // Summa: 5

Schema visivo

Firma Esempio Descrizione
Action
() => ...
Senza parametri, senza ritorno
Action<int>
x => ...
Un parametro
Action<int, string>
(x, s) => ...
Più parametri

Nella nostra applicazione

Aggiungiamo alla rubrica utenti un metodo per stampare tutti i nomi:

List<string> names = new() { "Anna", "Boris", "Vika" };
Action<string> printName = name => Console.WriteLine("Pol'zovatel': " + name);

names.ForEach(printName);
// oppure: names.ForEach(name => Console.WriteLine(name));

4. Predicate<T> — sì o no?

Quando serve una funzione che restituisca solo true o false per un parametro, usa Predicate<T>. È semplicemente un delegato che prende un parametro e restituisce bool.

Predicate è la "wrapper per quando ci serve un controllo booleano".

Esempi

1. Controllare se un numero è maggiore di 5

Predicate<int> isGreaterThanFive = x => x > 5;

Console.WriteLine(isGreaterThanFive(3)); // false
Console.WriteLine(isGreaterThanFive(7)); // true

2. Uso con il metodo List<T>.Find

List<int> values = new() { 2, 4, 7, 10 };
int found = values.Find(isGreaterThanFive); // usa Predicate<int>
Console.WriteLine(found); // 7

3. Tutti sono adulti?

List<int> ages = new() { 12, 19, 34 };

bool allAdults = ages.TrueForAll(age => age >= 18);
// TrueForAll accetta Predicate<int>

Qual è la differenza rispetto a Func<T, bool>?

Di fatto, sono completamente intercambiabili. Anche la documentazione Microsoft dice: "Predicate<T> è semplicemente un Func<T, bool> per API speciali". Però a volte i metodi della libreria standard richiedono proprio Predicate.

5. Come le lambda "si inseriscono" in Func, Action, Predicate

Quando scrivi una lambda, C# analizza: "Oh, la sua forma corrisponde al delegato richiesto — posso sostituirla!"

Func<int, int> f1 = x => x * 2;
Action<string> a1 = text => Console.WriteLine(text);
Predicate<int> p1 = x => x < 10;

Ovunque lambda! Ma sotto il cofano — tre delegati diversi con firme diverse.

Applicazione con codice "reale"

List<User> users = new() {
    new User("Anna", 24),
    new User("Boris", 17),
    new User("Vika", 31),
};

// Funzione che restituisce solo gli utenti adulti (Predicate<User>)
List<User> adults = users.FindAll(user => user.Age >= 18);
Console.WriteLine("Spisok vzroslykh: " + string.Join(", ", adults.Select(u => u.Name)));

E se vogliamo stampare i nomi di tutti gli utenti con Action<User>:

users.ForEach(user => Console.WriteLine(user.Name));

Ottenere i loro nomi (Func<User, string>):

IEnumerable<string> names = users.Select(user => user.Name);

Tabella per chiarezza

Delegato Firma Esempio di lambda Dove si usa
Func<T, U>
T → U
user => user.Name
Select, qualsiasi trasformazione
Action<T>
T → void
user => Console.WriteLine(...)
ForEach, metodi-azione
Predicate<T>
T → bool
user => user.Age > 18
Find, Exists, filtri

6. Esempi con l'app interna: passo dopo passo

Estendiamo la nostra piccola app "Rubrica utenti". Abbiamo una classe User:

public class User
{
    public string Name { get; }
    public int Age { get; }
    public bool IsActive { get; set; }

    public User(string name, int age)
    {
        Name = name;
        Age = age;
        IsActive = true;
    }
}

1. Func<User, bool> — controlliamo se l'utente è maggiorenne

Func<User, bool> isAdult = user => user.Age >= 18;

Usiamo in LINQ:

var adults = users.Where(isAdult);

2. Predicate<User> — cerchiamo un utente attivo

Predicate<User> isActive = user => user.IsActive;
User found = users.Find(isActive);

3. Action<User> — disattiviamo un utente

Action<User> deactivate = user => user.IsActive = false;
users.ForEach(deactivate);

4. Func<User, string> — otteniamo una breve descrizione

Func<User, string> describe = user => $"{user.Name} ({user.Age})";
var descriptions = users.Select(describe);

Tutte queste lambda sono codice come dati: si possono passare ai metodi, conservare in variabili, combinare.

7. Sfumature non ovvie ed errori tipici

1. La lambda deve corrispondere alla firma del delegato. Se la firma non coincide, avrai un errore di compilazione.

Func<int, string> wrong = x => x * 2; // errore: aspettato string, ricevuto int
// Corretto:
Func<int, string> right = x => (x * 2).ToString();

2. Non dimenticare void vs return. Action non restituisce valori — provare a scrivere tipo Action<int> a = x => x * x; non funzionerà, perché ritorna un valore quando non dovrebbe.

3. Predicate<T> e Func<T, bool> sono spesso intercambiabili, ma non sempre. A volte i metodi delle collection si aspettano proprio Predicate<T>, altre volte Func<T, bool>. L'assegnazione diretta non funziona senza wrapping esplicito.

Predicate<int> pred = x => x > 0;
Func<int, bool> func = pred; // errore
// Ma:
Func<int, bool> func2 = x => x > 0;
Predicate<int> pred2 = new Predicate<int>(func2); // si può convertire tramite il costruttore
2
Compito
C# SELF, livello 50, lezione 1
Bloccato
Utilizzo di `Action` per elaborare una lista di stringhe
Utilizzo di `Action` per elaborare una lista di stringhe
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION