CodeGym /Corsi /C# SELF /Espressioni lambda nelle collezioni e

Espressioni lambda nelle collezioni e LINQ

C# SELF
Livello 49 , Lezione 2
Disponibile

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
Where
Filtra gli elementi
Func<T, bool>
p => p.Price > 100
Select
Proietta, trasforma
Func<T, U>
p => p.Name
OrderBy
Ordinamento per chiave
Func<T, K>
u => u.Age
FirstOrDefault
Primo elemento che soddisfa la condizione
Func<T, bool>
u => u.Username == "Bob"
Any
Esiste almeno un elemento che soddisfa la condizione
Func<T, bool>
u => u.Age < 18
All
Tutti gli elementi soddisfano la condizione?
Func<T, bool>
u => u.Age >= 18
Count
Numero di elementi che soddisfano la condizione
Func<T, bool>
p => p.Price > 50
ForEach
Fai qualcosa per ogni elemento
Action<T>
u => Console.WriteLine(u.Name)
RemoveAll
Rimuove tutto secondo il predicato
Predicate<T>
u => u.Age < 18

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.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION