1. Introduzione
Quando guardi una serie TV, non scopri sempre tutte le info su ogni personaggio, solo quello che serve per la trama. Nel coding succede spesso una cosa simile: non ti serve tutto l’oggetto, ma solo alcuni suoi campi, valori o magari qualche calcolo su di essi.
Proiezione in LINQ — è la trasformazione degli elementi di una sequenza di partenza in una nuova forma. Di solito con il metodo Select. Puoi pensare a Select come a una macchina magica che prende ogni elemento, ci applica una funzione e ti restituisce il risultato. Tutto qui, niente trucchi.
Quando serve la proiezione?
- Vuoi mostrare solo i nomi degli utenti, non tutti i loro dati.
- Stai preparando una newsletter: dal cliente prendi solo indirizzo e nome.
- Fai un calcolo: aggiungi la tassa al prezzo del prodotto e ottieni una nuova collezione.
- Per la UI: devi mostrare solo una parte dei dati, senza appesantire l’interfaccia.
2. Metodo Select: sintassi e base
Sintassi base
LINQ supporta due stili: Method Syntax e Query Syntax. Per Select il risultato è lo stesso in entrambi.
Method Syntax
var query = myCollection.Select(x => x.CosaRestituire);
Qui x è la variabile per ogni elemento della collezione, e a destra c’è l’espressione che trasforma l’elemento in quello che ti serve.
Query Syntax
var query = from x in myCollection
select x.CosaRestituire;
Qui sembra un po’ più SQL.
Esempio semplice: lista di numeri
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// Voglio ottenere i loro quadrati
var squares = numbers.Select(n => n * n);
foreach (var s in squares)
{
Console.WriteLine(s); // 1, 4, 9, 16, 25
}
Abbiamo appena trasformato una collezione di numeri in una collezione dei loro quadrati — al volo, senza fare un ciclo manuale!
Esempio 2
Negli esempi precedenti avevamo già una classe Product:
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
Supponiamo di avere una lista:
var products = new List<Product>
{
new Product { Name = "Cioccolato", Price = 120 },
new Product { Name = "Formaggio", Price = 250 },
new Product { Name = "Pane", Price = 55 }
};
Se vuoi solo i nomi di tutti i prodotti, non ti serve tutto l’oggetto, giusto? Ti serve solo la lista dei nomi:
var names = products.Select(p => p.Name);
foreach (var name in names)
{
Console.WriteLine(name); // Cioccolato, Formaggio, Pane
}
3. Collezione restituita — quali opzioni ci sono?
Nota che Select restituisce sempre una collezione (più precisamente — IEnumerable<TOutput>), dove TOutput è il tipo restituito dalla funzione. Sei tu a decidere cosa sarà: stringa, numero, tipo anonimo, anche un nuovo oggetto.
Proiezione in un nuovo tipo
Per esempio, vuoi ottenere non l’oggetto grezzo, ma la sua "proiezione":
var projections = products.Select(p => new { p.Name, PrezzoConIVA = p.Price * 1.2 });
foreach (var item in projections)
{
Console.WriteLine($"{item.Name}: {item.PrezzoConIVA}");
}
Qui abbiamo creato una nuova collezione di oggetti anonimi — con nome e prezzo con una IVA immaginaria!
4. Restituire una nuova classe o un tipo anonimo
Puoi scegliere: creare classi vere e proprie per il risultato o usare tipi anonimi se la struttura ti serve solo "qui e ora".
Tipi anonimi
var result = products.Select(p => new { Nome = p.Name, PrezzoVecchio = p.Price, PrezzoNuovo = p.Price + 40 });
foreach (var p in result)
{
Console.WriteLine($"{p.Nome}: Era {p.PrezzoVecchio}, ora {p.PrezzoNuovo}");
}
I tipi anonimi sono comodi per creare risultati al volo, soprattutto quando non ha senso creare una classe solo per una singola operazione.
Usare una propria classe per la proiezione
Creiamo una nuova classe:
public class ProductDto
{
public string Name { get; set; }
public double PriceWithTax { get; set; }
}
Ora dentro la query LINQ:
var productsWithTax = products.Select(p => new ProductDto
{
Name = p.Name,
PriceWithTax = p.Price * 1.2
});
foreach (var p in productsWithTax)
{
Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}
5. Accesso a collezioni e proprietà interne
E se dentro l’oggetto c’è una collezione? Tipo, ogni utente ha una lista di acquisti.
public class User
{
public string Name { get; set; }
public List<Product> Purchases { get; set; }
}
Vuoi ottenere la lista di tutte le liste di acquisti? Nessun problema!
var allPurchases = users.Select(u => u.Purchases);
foreach (var purchaseList in allPurchases)
{
foreach (var product in purchaseList)
{
Console.WriteLine(product.Name);
}
}
Importante! In questo caso ottieni una collezione di collezioni, non una lista "piatta" di prodotti. Come ottenere UNA sola lista con tutti i prodotti di tutti gli utenti? Questo sarà il tema della prossima lezione — lì conosceremo SelectMany.
6. Sfumature utili
Conversione al tipo di collezione: ToList() e amici
Select restituisce IEnumerable<T>. Se vuoi una lista normale (List<T>), basta aggiungere .ToList():
var nameList = products.Select(p => p.Name).ToList();
Ora hai una lista vera e propria, con cui puoi lavorare come al solito.
Usare l’indice dell’elemento
A volte è utile sapere quale elemento della collezione stai processando. Il metodo Select ha una overload:
var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
Console.WriteLine($"{item.Index}: {item.Name}");
}
Così puoi, per esempio, preparare dati per liste numerate.
Proiezione nel coding
Essere bravi con Select è una domanda frequente ai colloqui. Senza questa roba è difficile immaginare app moderne:
- Selezione solo dei dati necessari dal DB o da servizi esterni.
- Preparazione dei dati da passare tra i layer dell’app.
- Trasformazione dei dati in formato adatto per la visualizzazione o per invio in rete.
- Risparmio di memoria (non ti porti dietro tutto l’oggetto, solo i campi che servono).
Le aziende che lavorano con grandi quantità di dati spesso basano su questo la loro business logic.
7. Errori tipici e particolarità
A volte chi inizia si becca bug fastidiosi — di solito perché non capisce bene come funziona esecuzione pigra (deferred execution) in LINQ. Per esempio, dopo aver chiamato Select non sempre la query viene eseguita subito — solo quando davvero iteri la collezione (foreach, ToList(), ecc). Questo ti permette di costruire una "pipeline di elaborazione", ma a volte fa sì che il risultato cambi se i dati di partenza sono stati modificati prima di usare il risultato della query.
Ricorda anche che Select non modifica la collezione di partenza — ne crea una nuova. Quindi se ti aspetti di trovare nuovi campi in products dopo products.Select(...), non succederà.
Gestione dei null: se la proiezione prevede di accedere a campi che possono essere null, ricordati di gestirlo. In C# 8+ il compilatore ti avvisa se puoi beccarti un NullReferenceException.
GO TO FULL VERSION