1. Modi base per filtrare le collezioni
Il filtraggio è come un setaccio per l'oro, solo che invece delle pepite scegliamo dalla collezione solo gli elementi che ci servono. Per esempio, abbiamo una lista di utenti — vogliamo trovare solo i maggiorenni, oppure solo gli studenti, oppure solo quelli che amano il caffè (insomma, i programmatori). Selezionare gli elementi giusti è una cosa che capita ovunque: dal lavoro con i DB alla gestione dell'input dell'utente.
Vediamo diversi modi per filtrare in C#. Man mano che impariamo, svilupperemo la nostra app console didattica aggiungendo filtri.
Filtraggio manuale con il ciclo
Partiamo dal modo più semplice e classico: filtrare con foreach:
// Esempio: abbiamo una lista di numeri, dobbiamo prendere solo i pari
List<int> numeri = new List<int> { 1, 2, 3, 4, 5, 6 };
List<int> numeriPari = new List<int>();
foreach (int numero in numeri)
{
if (numero % 2 == 0) // se il numero è pari
{
numeriPari.Add(numero);
}
}
Console.WriteLine("Numeri pari:");
foreach (int n in numeriPari)
{
Console.WriteLine(n);
}
Questo metodo funziona sempre, è facile da capire, ma non è il più compatto e nemmeno il più "moderno".
Perché il filtraggio manuale non è sempre comodo?
Quando hai un'altra collezione, un altro criterio di filtro, o devi scrivere più filtri insieme — ti ritrovi con tanto codice simile. Vorresti più compattezza, leggibilità e modularità.
2. Filtraggio con criteri complessi
Supponiamo di avere una collezione di oggetti. Prendiamo l'esempio degli utenti dalla nostra app in sviluppo.
public class User
{
public string Nome { get; set; }
public int Eta { get; set; }
public bool EStudente { get; set; }
}
Dichiariamo una lista di utenti:
List<User> utenti = new List<User>
{
new User { Nome = "Anja", Eta = 17, EStudente = true },
new User { Nome = "Boris", Eta = 21, EStudente = false },
new User { Nome = "Vika", Eta = 19, EStudente = true },
new User { Nome = "Gleb", Eta = 25, EStudente = false }
};
Esempio 1: Selezionare tutti gli studenti
List<User> studenti = new List<User>();
foreach (User utente in utenti)
{
if (utente.EStudente)
studenti.Add(utente);
}
Console.WriteLine("Lista degli studenti:");
foreach (User utente in studenti)
{
Console.WriteLine($"{utente.Nome} ({utente.Eta})");
}
Esempio 2: Selezionare solo gli studenti maggiorenni
List<User> maggiorenni = new List<User>();
foreach (User utente in utenti)
{
if (utente.Eta >= 18 && utente.EStudente)
maggiorenni.Add(utente);
}
Console.WriteLine("Studenti maggiorenni:");
foreach (User utente in maggiorenni)
{
Console.WriteLine($"{utente.Nome} ({utente.Eta})");
}
E se i criteri di filtro sono dinamici?
Puoi mettere le condizioni di filtro in metodi separati, così li richiami dentro il ciclo:
bool EMaggiore(User u) { return u.Eta >= 18; }
bool EStudente(User u) { return u.EStudente; }
List<User> filtrati = new List<User>();
foreach (User utente in utenti)
{
if (EMaggiore(utente) && EStudente(utente))
filtrati.Add(utente);
}
3. Filtraggio per chiave nei dizionari
Liste e array sono comodi, ma spesso lavoriamo anche con i dizionari.
Supponiamo di avere un dizionario dove la chiave è il nome dell'utente e il valore è la sua età:
Dictionary<string, int> etaPerNome = new Dictionary<string, int>
{
["Anja"] = 17,
["Boris"] = 21,
["Vika"] = 19,
["Gleb"] = 25
};
Obiettivo: selezionare solo utenti 18+
Dictionary<string, int> maggiorenni = new Dictionary<string, int>();
foreach (var coppia in etaPerNome)
{
if (coppia.Value >= 18)
maggiorenni.Add(coppia.Key, coppia.Value);
}
Console.WriteLine("Maggiorenni:");
foreach (var coppia in maggiorenni)
{
Console.WriteLine($"{coppia.Key}: {coppia.Value} anni");
}
Obiettivo: selezionare utenti con nomi che iniziano per "V"
Dictionary<string, int> nomiConV = new Dictionary<string, int>();
foreach (var coppia in etaPerNome)
{
if (coppia.Key.StartsWith("V"))
nomiConV.Add(coppia.Key, coppia.Value);
}
Console.WriteLine("Nomi che iniziano con 'V':");
foreach (var coppia in nomiConV)
{
Console.WriteLine($"{coppia.Key}: {coppia.Value} anni");
}
4. Principi del filtraggio
Come funziona il processo di filtraggio
┌──────────────────────────────────┐
│ Lista: [1, 2, 3, 4, 5, 6] │
└──────────────────────────────────┘
│
▼
[Controllo: n % 2 == 0]
│
▼
┌──────────────────────────────────┐
│ Risultato: [2, 4, 6] │
└──────────────────────────────────┘
Composizione dei filtri: filtrare con condizioni diverse
Puoi fare filtraggi in sequenza, se vuoi selezionare i dati a step:
// Prima filtriamo solo gli studenti
List<User> soloStudenti = new List<User>();
foreach (User utente in utenti)
{
if (utente.EStudente)
soloStudenti.Add(utente);
}
// Poi tra loro scegliamo solo i maggiorenni
List<User> studentiMaggiorenni = new List<User>();
foreach (User utente in soloStudenti)
{
if (utente.Eta >= 18)
studentiMaggiorenni.Add(utente);
}
Oppure tutto insieme:
List<User> studentiMaggiorenni = new List<User>();
foreach (User utente in utenti)
{
if (utente.EStudente && utente.Eta >= 18)
studentiMaggiorenni.Add(utente);
}
Ordinamento e altre operazioni
L'ordinamento si fa con il metodo Sort per le liste:
// Studenti maggiorenni ordinati per età
studentiMaggiorenni.Sort((a, b) => a.Eta.CompareTo(b.Eta));
Parleremo meglio dell'ordinamento nella prossima lezione!
5. Filtraggio negli scenari utente
1. Filtraggio dell'input utente
Supponiamo che la nostra app riceva una lista di voti e debba selezionare tutti i "cinque".
Console.Write("Inserisci i voti separati da spazio: ");
string input = Console.ReadLine();
List<int> voti = new List<int>();
foreach (string s in input.Split(' '))
{
if (int.TryParse(s, out int voto))
voti.Add(voto);
}
List<int> cinque = new List<int>();
foreach (int voto in voti)
{
if (voto == 5)
cinque.Add(voto);
}
Console.WriteLine("Cinque:");
foreach (var voto in cinque)
{
Console.WriteLine(voto);
}
2. Filtraggio con più condizioni
Obiettivo: selezionare utenti-studenti con età da 18 a 22 anni inclusi.
List<User> filtrati = new List<User>();
foreach (User utente in utenti)
{
if (utente.EStudente && utente.Eta >= 18 && utente.Eta <= 22)
filtrati.Add(utente);
}
3. Filtraggio per presenza di un elemento
Abbiamo una lista di stringhe e vogliamo selezionare solo quelle che contengono la sottostringa ".net" (senza distinzione tra maiuscole e minuscole).
List<string> tecnologie = new List<string> { "C#", ".NET", "Java", "dotnet", "JavaScript" };
List<string> netTechs = new List<string>();
foreach (var tech in tecnologie)
{
if (tech.ToLower().Contains(".net"))
netTechs.Add(tech);
}
foreach (var tech in netTechs)
{
Console.WriteLine(tech);
}
6. Caratteristiche e errori tipici nel filtraggio
Il filtraggio sembra facile, ma qui si può fare casino. Vediamo alcune particolarità.
Modifica della collezione originale
Se filtri la collezione originale e poi la cambi dopo il filtraggio, — il risultato non cambia, se hai già creato una nuova lista. Se invece hai solo salvato il riferimento alla vecchia lista, i cambiamenti nella collezione originale possono influenzare il risultato.
List<int> filtrati = new List<int>();
foreach (int n in numeri)
{
if (n > 2)
filtrati.Add(n);
}
// Modifichiamo la lista originale
numeri.Add(10);
foreach (var n in filtrati)
{
Console.WriteLine(n); // 10 non ci sarà
}
Risultato del filtraggio manuale
Il risultato del filtraggio manuale di solito è una nuova lista modificabile (tipo List<T>), con cui puoi fare tutto quello che vuoi — aggiungere, togliere elementi ecc.
Filtraggio e performance
I filtri nei cicli vengono chiamati per ogni elemento della collezione, quindi se la funzione dentro la condizione è pesante — occhio. A volte è meglio fare un semplice ciclo foreach, soprattutto se hai logica complessa e ti serve fare logging o altre azioni.
GO TO FULL VERSION