Array e Collezioni in C#

Organizzare e gestire gruppi di dati correlati con strutture efficienti

🎯 Cosa Imparerai in Questa Lezione

Finora abbiamo lavorato con singole variabili, ma cosa succede quando devi gestire
centinaia di valori? Gli array e le collezioni
sono strutture che permettono di memorizzare e manipolare gruppi di dati correlati in modo efficiente.

💡 Perché Usare Array e Collezioni?

Immagina di dover memorizzare i voti di 30 studenti. Senza array dovresti creare 30 variabili
(voto1, voto2, …, voto30). Con un array?
Una sola variabile: int[] voti = new int[30];

📊 Array vs Collezioni
Array

  • Dimensione fissa
  • Più performanti
  • Sintassi semplice
  • Tipo primitivo
Collezioni (List, Dictionary)

  • Dimensione dinamica
  • Più funzionalità
  • Metodi built-in
  • Più flessibili

📦 Array

Un array è una struttura dati che contiene un numero fisso di
elementi dello stesso tipo, organizzati in sequenza e accessibili tramite un indice numerico
che parte da 0.

💡 Come Funziona un Array

Pensa a un array come a una fila di scatole numerate. Ogni scatola può contenere un valore
dello stesso tipo. La prima scatola ha indice 0, la seconda 1, e così via.

Array: [ 10 | 20 | 30 | 40 | 50 ]
Indice:   0    1    2    3    4

Dichiarazione e Inizializzazione

1
Dichiarazione Base

Dichiara un array specificando il tipo seguito da [].

Dichiarazione Array
// Solo dichiarazione (array non inizializzato)
int[] numeri;

// Dichiarazione + inizializzazione con dimensione
numeri = new int[5];  // Array di 5 interi (tutti inizializzati a 0)

// In una sola riga
string[] nomi = new string[3];  // Array di 3 stringhe (null)

2
Inizializzazione con Valori

Puoi inizializzare l’array direttamente con i valori.

Array con Valori Iniziali
// Sintassi completa
int[] voti = new int[] { 28, 30, 25, 27, 29 };

// Sintassi abbreviata (consigliata)
string[] nomi = { "Mario", "Luigi", "Peach" };

// Array di double
double[] temperature = { 18.5, 22.3, 19.8, 21.0 };

// Il compilatore conta automaticamente gli elementi!

3
Accesso e Modifica Elementi

Usa l’indice tra parentesi quadre per leggere o modificare elementi.

Accesso agli Elementi
int[] voti = { 28, 30, 25, 27, 29 };

// Lettura (gli indici partono da 0!)
Console.WriteLine(voti[0]);  // Output: 28 (primo elemento)
Console.WriteLine(voti[2]);  // Output: 25 (terzo elemento)

// Modifica
voti[0] = 29;  // Cambia il primo voto a 29
voti[4] = 30;  // Cambia l'ultimo voto a 30

// Lunghezza dell'array
Console.WriteLine(voti.Length);  // Output: 5

// Ultimo elemento (usando Length)
int ultimoVoto = voti[voti.Length - 1];  // voti[4]

⚠️ ATTENZIONE: IndexOutOfRangeException

Gli indici degli array partono da 0. In un array di 5 elementi,
gli indici validi sono da 0 a 4. Accedere a un indice fuori range causa un’eccezione!

int[] numeri = new int[5];  // Indici validi: 0, 1, 2, 3, 4

Console.WriteLine(numeri[0]);   // ✅ OK
Console.WriteLine(numeri[4]);   // ✅ OK
Console.WriteLine(numeri[5]);   // ❌ ERRORE! IndexOutOfRangeException
Console.WriteLine(numeri[-1]);  // ❌ ERRORE! Indice negativo

Array Multidimensionali

C# supporta array con più dimensioni, utili per rappresentare dati tabulari,
matrici, griglie di gioco, ecc.

1
Array Bidimensionali (Matrici)

Rappresenta dati in righe e colonne, come una tabella.

Array 2D – Matrice
// Dichiarazione: [righe, colonne]
int[,] matrice = new int[3, 2] 
{
    { 1, 2 },  // Riga 0
    { 3, 4 },  // Riga 1
    { 5, 6 }   // Riga 2
};

// Accesso agli elementi: [riga, colonna]
Console.WriteLine(matrice[0, 0]);  // Output: 1
Console.WriteLine(matrice[0, 1]);  // Output: 2
Console.WriteLine(matrice[2, 1]);  // Output: 6

// Modifica
matrice[1, 0] = 10;  // Cambia 3 → 10

// Dimensioni
int righe = matrice.GetLength(0);     // 3 righe
int colonne = matrice.GetLength(1);   // 2 colonne

// Visualizzazione matrice
Console.WriteLine("Matrice:");
/*
  1  2
  10 4
  5  6
*/

2
Array Jagged (Array di Array)

Ogni “riga” può avere lunghezza diversa. Più flessibile delle matrici.

Array Jagged
// Dichiarazione: [][] (doppia parentesi quadra)
int[][] arrayJagged = new int[3][];

// Inizializzazione di ogni riga (possono avere lunghezze diverse!)
arrayJagged[0] = new int[] { 1, 2, 3 };        // 3 elementi
arrayJagged[1] = new int[] { 4, 5 };           // 2 elementi
arrayJagged[2] = new int[] { 6, 7, 8, 9 };     // 4 elementi

// Accesso: [riga][colonna]
Console.WriteLine(arrayJagged[0][0]);  // Output: 1
Console.WriteLine(arrayJagged[2][3]);  // Output: 9

// Lunghezza di ogni riga
Console.WriteLine(arrayJagged[0].Length);  // 3
Console.WriteLine(arrayJagged[1].Length);  // 2
Console.WriteLine(arrayJagged[2].Length);  // 4

📊 Matrice vs Jagged Array
Caratteristica Matrice [,] Jagged Array [][]
Struttura Righe tutte uguali Righe di lunghezza variabile
Sintassi accesso arr[i, j] arr[i][j]
Performance Più veloce Leggermente più lento
Uso principale Dati tabulari regolari Dati di lunghezza variabile

📚 Collezioni Generiche

Le collezioni sono strutture dati più flessibili degli array, che possono
crescere o ridursi dinamicamente. C# offre diverse collezioni nel namespace
System.Collections.Generic.

💡 Perché Usare le Collezioni?

Gli array hanno dimensione fissa: se dichiari int[] numeri = new int[10];,
puoi contenere solo 10 elementi. Con le collezioni come List<int>,
puoi aggiungere o rimuovere elementi dinamicamente senza preoccuparti della dimensione!

List<T> – Lista Dinamica

List<T> è la collezione più utilizzata. È come un array che può
crescere automaticamente quando aggiungi elementi.

List – Creazione e Operazioni Base
using System.Collections.Generic;

// Creazione (inizialmente vuota)
List<string> studenti = new List<string>();

// Aggiunta di elementi (alla fine)
studenti.Add("Marco");
studenti.Add("Anna");
studenti.Add("Luca");
// Lista: ["Marco", "Anna", "Luca"]

// Inserimento in posizione specifica
studenti.Insert(1, "Sara");  // Inserisce "Sara" all'indice 1
// Lista: ["Marco", "Sara", "Anna", "Luca"]

// Accesso agli elementi (come array)
Console.WriteLine(studenti[0]);  // Output: Marco

// Modifica
studenti[2] = "Anna Maria";

// Numero di elementi
Console.WriteLine(studenti.Count);  // Output: 4

List – Rimozione e Ricerca
List<string> studenti = new List<string> 
{ 
    "Marco", "Sara", "Anna", "Luca" 
};

// Rimozione per valore
studenti.Remove("Luca");  // Rimuove "Luca"
// Lista: ["Marco", "Sara", "Anna"]

// Rimozione per indice
studenti.RemoveAt(0);  // Rimuove il primo elemento
// Lista: ["Sara", "Anna"]

// Verifica presenza
bool contiene = studenti.Contains("Anna");  // true
bool contieneLuca = studenti.Contains("Luca");  // false

// Trova indice
int indice = studenti.IndexOf("Anna");  // 1
int nonTrovato = studenti.IndexOf("Paolo");  // -1 (non esiste)

// Pulizia completa
studenti.Clear();  // Svuota la lista
Console.WriteLine(studenti.Count);  // 0

📌 Metodi Principali di List<T>

Metodo Descrizione Esempio
Add(item) Aggiunge alla fine lista.Add("nuovo")
Insert(index, item) Inserisce in posizione lista.Insert(0, "primo")
Remove(item) Rimuove per valore lista.Remove("Mario")
RemoveAt(index) Rimuove per indice lista.RemoveAt(2)
Contains(item) Verifica presenza lista.Contains("Anna")
IndexOf(item) Trova indice lista.IndexOf("Luca")
Clear() Svuota lista lista.Clear()
Count Numero elementi lista.Count

Dictionary<TKey, TValue> – Dizionario

Un dizionario è una collezione di coppie chiave-valore,
dove ogni chiave è unica. Perfetto per cercare valori rapidamente usando una chiave.

💡 Quando Usare un Dictionary

Immagina un registro studenti: invece di cercare il voto di “Marco” scorrendo tutta una lista,
con un dizionario puoi accedere direttamente: votiStudenti["Marco"].
Velocissimo! ⚡

Dictionary – Creazione e Operazioni
using System.Collections.Generic;

// Creazione
Dictionary<string, int> votiStudenti = new Dictionary<string, int>();

// Aggiunta elementi - Metodo 1
votiStudenti.Add("Marco", 28);
votiStudenti.Add("Anna", 30);

// Aggiunta elementi - Metodo 2 (sintassi con [])
votiStudenti["Luca"] = 25;
votiStudenti["Sara"] = 27;

// Accesso ai valori tramite chiave
Console.WriteLine(votiStudenti["Anna"]);  // Output: 30

// Modifica valore
votiStudenti["Marco"] = 29;  // Aggiorna da 28 a 29

// Numero di elementi
Console.WriteLine(votiStudenti.Count);  // 4

Dictionary – Verifica e Rimozione
Dictionary<string, int> votiStudenti = new Dictionary<string, int>
{
    { "Marco", 28 },
    { "Anna", 30 },
    { "Luca", 25 }
};

// Verifica esistenza chiave
if (votiStudenti.ContainsKey("Marco"))
{
    Console.WriteLine($"Voto di Marco: {votiStudenti["Marco"]}");
}

// TryGetValue - Metodo sicuro (consigliato!)
if (votiStudenti.TryGetValue("Paolo", out int voto))
{
    Console.WriteLine($"Voto: {voto}");
}
else
{
    Console.WriteLine("Paolo non trovato");  // Questo viene eseguito
}

// Rimozione
votiStudenti.Remove("Luca");  // Rimuove Luca

// Pulizia
votiStudenti.Clear();

Dictionary – Iterazione
Dictionary<string, int> votiStudenti = new Dictionary<string, int>
{
    { "Marco", 28 },
    { "Anna", 30 },
    { "Luca", 25 }
};

// Iterazione su tutte le coppie
foreach (var coppia in votiStudenti)
{
    Console.WriteLine($"{coppia.Key}: {coppia.Value}");
}
// Output:
// Marco: 28
// Anna: 30
// Luca: 25

// Solo chiavi
foreach (string nome in votiStudenti.Keys)
{
    Console.WriteLine(nome);
}

// Solo valori
foreach (int voto in votiStudenti.Values)
{
    Console.WriteLine(voto);
}

⚠️ ATTENZIONE: Chiavi Univoche

Ogni chiave deve essere unica. Se provi ad aggiungere una chiave
già esistente con Add(), otterrai un’eccezione!

Dictionary<string, int> voti = new Dictionary<string, int>();

voti.Add("Marco", 28);
voti.Add("Marco", 30);  // ❌ ERRORE! Chiave già esistente

// ✅ SOLUZIONE 1: Usa [] per sovrascrivere
voti["Marco"] = 30;  // OK, aggiorna il valore

// ✅ SOLUZIONE 2: Verifica prima
if (!voti.ContainsKey("Marco"))
{
    voti.Add("Marco", 28);
}

Stack<T> e Queue<T>

Stack (pila) e Queue (coda) implementano strutture dati speciali
per gestire elementi in ordine specifico.

1
Stack<T> – Pila LIFO (Last-In-First-Out)

L’ultimo elemento inserito è il primo ad uscire (come una pila di piatti).

Stack – Pila
using System.Collections.Generic;

Stack<string> pila = new Stack<string>();

// Push - Aggiunge in cima
pila.Push("Primo");   // ← Bottom
pila.Push("Secondo");
pila.Push("Terzo");   // ← Top

// Pop - Rimuove dalla cima
string ultimo = pila.Pop();  // "Terzo"
string penultimo = pila.Pop();  // "Secondo"

// Peek - Guarda la cima senza rimuovere
string top = pila.Peek();  // "Primo"

// Count
Console.WriteLine(pila.Count);  // 1

2
Queue<T> – Coda FIFO (First-In-First-Out)

Il primo elemento inserito è il primo ad uscire (come una fila alla cassa).

Queue – Coda
using System.Collections.Generic;

Queue<string> coda = new Queue<string>();

// Enqueue - Aggiunge in fondo
coda.Enqueue("Primo");    // ← Front
coda.Enqueue("Secondo");
coda.Enqueue("Terzo");    // ← Back

// Dequeue - Rimuove dal fronte
string primo = coda.Dequeue();  // "Primo"
string secondo = coda.Dequeue();  // "Secondo"

// Peek - Guarda il fronte senza rimuovere
string front = coda.Peek();  // "Terzo"

// Count
Console.WriteLine(coda.Count);  // 1

🎯 Quando Usare Stack e Queue
  • Stack: Undo/Redo, navigazione browser (cronologia), parsing espressioni
  • Queue: Gestione task, stampa documenti, messaggi, BFS algoritmi

🔁 Iterazione su Array e Collezioni

Il modo più comune per processare tutti gli elementi di un array o collezione è usare un ciclo.

1
Ciclo for – Ideale per Array

Quando serve l’indice o quando devi modificare gli elementi.

Ciclo for con Array
int[] numeri = { 10, 20, 30, 40, 50 };

// Iterazione classica
for (int i = 0; i < numeri.Length; i++)
{
    Console.WriteLine($"Elemento {i}: {numeri[i]}");
}

// Modifica elementi (raddoppia tutti)
for (int i = 0; i < numeri.Length; i++)
{
    numeri[i] *= 2;
}
// Risultato: [20, 40, 60, 80, 100]

2
Ciclo foreach - Ideale per Lettura

Più semplice e leggibile quando non serve l'indice.

Ciclo foreach
// Con array
int[] numeri = { 10, 20, 30, 40, 50 };
foreach (int numero in numeri)
{
    Console.WriteLine(numero);
}

// Con List
List<string> città = new List<string> 
{ 
    "Roma", "Milano", "Napoli" 
};
foreach (string città in città)
{
    Console.WriteLine(città);
}

// Con Dictionary
Dictionary<string, int> voti = new Dictionary<string, int>
{
    { "Marco", 28 },
    { "Anna", 30 }
};
foreach (var coppia in voti)
{
    Console.WriteLine($"{coppia.Key}: {coppia.Value}");
}

📊 for vs foreach - Quando Usarli

Usa for quando... Usa foreach quando...
Serve l'indice corrente Serve solo leggere i valori
Devi modificare gli elementi Vuoi codice più leggibile
Iteri all'indietro o con step Iteri tutte le collezioni
Serve controllo fine iterazione Non serve l'indice

🚀 Esempio Completo: Sistema Gestione Studenti

Vediamo un esempio completo che utilizza List e Dictionary per gestire studenti e voti.

Program.cs - Gestione Studenti
using System;
using System.Collections.Generic;

class Program 
{
    static void Main() 
    {
        Console.WriteLine("=== SISTEMA GESTIONE STUDENTI ===\n");
        
        // Lista studenti
        List<string> studenti = new List<string> 
        {
            "Marco Rossi",
            "Anna Verdi",
            "Luca Bianchi",
            "Sara Neri",
            "Paolo Gialli"
        };
        
        // Dizionario: studente → lista voti
        Dictionary<string, List<int>> votiStudenti = 
            new Dictionary<string, List<int>>();
        
        // Genera voti casuali per ogni studente
        Random random = new Random();
        foreach (string studente in studenti) 
        {
            List<int> voti = new List<int>();
            
            // 5 voti per studente
            for (int i = 0; i < 5; i++) 
            {
                voti.Add(random.Next(18, 31));  // 18-30
            }
            
            votiStudenti.Add(studente, voti);
        }
        
        // CALCOLO E VISUALIZZAZIONE MEDIE
        Console.WriteLine("📊 Medie degli studenti:\n");
        
        Dictionary<string, double> medie = new Dictionary<string, double>();
        
        foreach (var coppia in votiStudenti) 
        {
            string studente = coppia.Key;
            List<int> voti = coppia.Value;
            
            // Calcola media
            double somma = 0;
            foreach (int voto in voti) 
            {
                somma += voto;
            }
            double media = somma / voti.Count;
            medie.Add(studente, media);
            
            // Mostra dettagli
            Console.WriteLine($"👤 {studente}");
            Console.Write("   Voti: ");
            foreach (int voto in voti) 
            {
                Console.Write($"{voto} ");
            }
            Console.WriteLine($"\n   Media: {media:F2}");
            Console.WriteLine();
        }
        
        // TROVA MIGLIORE STUDENTE
        string miglioreStudente = "";
        double mediaMax = 0;
        
        foreach (var coppia in medie) 
        {
            if (coppia.Value > mediaMax) 
            {
                mediaMax = coppia.Value;
                miglioreStudente = coppia.Key;
            }
        }
        
        Console.WriteLine($"🏆 Studente con media più alta:");
        Console.WriteLine($"   {miglioreStudente} - Media: {mediaMax:F2}");
        
        // TROVA PEGGIORE STUDENTE
        string peggioreStudente = "";
        double mediaMin = 30;
        
        foreach (var coppia in medie) 
        {
            if (coppia.Value < mediaMin) 
            {
                mediaMin = coppia.Value;
                peggioreStudente = coppia.Key;
            }
        }
        
        Console.WriteLine($"\n⚠️  Studente che necessita supporto:");
        Console.WriteLine($"   {peggioreStudente} - Media: {mediaMin:F2}");
        
        // STATISTICHE GENERALI
        double mediaTotale = 0;
        foreach (double media in medie.Values) 
        {
            mediaTotale += media;
        }
        mediaTotale /= medie.Count;
        
        Console.WriteLine($"\n📈 Statistiche:");
        Console.WriteLine($"   Totale studenti: {studenti.Count}");
        Console.WriteLine($"   Media classe: {mediaTotale:F2}");
        Console.WriteLine($"   Range: {mediaMin:F2} - {mediaMax:F2}");
    }
}

💡 Analisi dell'Architettura
  • List<string> - Per memorizzare nomi studenti
  • Dictionary<string, List<int>> - Mappa studente → voti
  • Dictionary<string, double> - Cache delle medie calcolate
  • foreach - Per iterare su collezioni
  • Random - Per generare voti casuali
  • Calcoli statistici - Media, min, max

💪 Esercizi Pratici

📝 Esercizio 1: Agenda Contatti

Crea un'agenda telefonica usando un Dictionary:

  • Chiave: nome contatto (string)
  • Valore: numero telefono (string)
  • Implementa menu con: Aggiungi, Cerca, Rimuovi, Mostra tutti
  • Gestisci contatti duplicati
  • Permetti ricerca case-insensitive

📝 Esercizio 2: Gestione Inventario Negozio

Crea un sistema di inventario con:

  • Dictionary per prodotti: nome → quantità
  • Dictionary per prezzi: nome → prezzo
  • Metodi: AggiungiProdotto, VendiProdotto, RifornisciProdotto
  • Calcola valore totale inventario
  • Mostra prodotti in esaurimento (quantità < 5)

🎯 Riepilogo Lezione

Array:

  • Dimensione fissa
  • Accesso per indice (0-based)
  • Array multidimensionali [,]
  • Array jagged [][]
Collezioni:

  • List<T> - dinamica
  • Dictionary<K,V> - chiave-valore
  • Stack<T> - LIFO
  • Queue<T> - FIFO

Lezione 12 del Corso C# Completo

Torna in alto