1. Introduzione
Immagina: hai appena trovato lavoro in un laboratorio segreto che studia i gatti. Ti danno un compito — creare un dizionario elettronico dei gatti, dove puoi scoprire velocemente l'età, il colore e la lunghezza della coda di un gatto in base al suo nome. La soluzione pronta già esiste — è Dictionary<string, Cat>. Ma ecco il problema: più tardi il capo dice che per alcune razze rare serve che il dizionario mantenga l'ordine di inserimento, e a volte deve anche essere cancellato secondo una logica speciale.
E se dovessi scrivere un metodo che accetta qualsiasi "dizionario di gatti"? Non importa quale classe concreta — l'importante è che supporti le operazioni base "chiave-valore". Ed è qui che entra in gioco l'interfaccia IDictionary<TKey, TValue>.
IDictionary<TKey, TValue> è un contratto generico che definisce cos'è un dizionario in .NET. Non è una classe concreta, ma un'interfaccia, cioè un insieme di regole che ogni vero dizionario deve rispettare:
- Ricerca veloce per chiave
- Aggiunta e rimozione di coppie "chiave-valore"
- Iterazione su tutte le coppie
- Controllo dell'esistenza di una chiave
Se Java fosse esistita nel Medioevo, i suoi cavalieri invece delle spade contro i draghi avrebbero brandito chiavi e valori. E gli sviluppatori C# avrebbero semplicemente implementato IDictionary<TKey, TValue> — e il drago (cioè il tuo codice) sarebbe stato sconfitto.
2. Tabella dell'interfaccia IDictionary
Diamo un'occhiata alle principali "promesse" (membri) di questa interfaccia:
| Membro dell'interfaccia | Tipo | Descrizione |
|---|---|---|
|
Proprietà | Indicizzatore. Permette di ottenere o impostare il valore associato a una chiave specifica. In lettura: se la chiave non esiste, lancia KeyNotFoundException. In scrittura: se la chiave non esiste, aggiunge una nuova coppia; se la chiave esiste, aggiorna il valore. Importante: è il modo più comune di lavorare con i dizionari. |
|
Proprietà | Restituisce una collezione che contiene tutte le chiavi nel dizionario. Permette di iterare solo sulle chiavi. |
|
Proprietà | Restituisce una collezione che contiene tutti i valori nel dizionario. Permette di iterare solo sui valori. |
|
Metodo | Aggiunge la chiave e il valore specificati al dizionario. Se la chiave esiste già, genera ArgumentException. |
|
Metodo | Determina se il dizionario contiene un elemento con la chiave specificata. Restituisce true se la chiave è trovata; altrimenti — false. Molto utile per evitare errori quando si tenta di accedere a una chiave inesistente tramite l'indicizzatore. |
|
Metodo | Rimuove l'elemento con la chiave specificata dal dizionario. Restituisce true se l'elemento è stato trovato e rimosso con successo; altrimenti — false (ad esempio, se la chiave non è stata trovata). |
|
Metodo | Ottiene il valore associato alla chiave specificata. È un modo sicuro per ottenere un valore se non sei sicuro che la chiave esista. Restituisce true se la chiave è trovata e value contiene il valore corrispondente; altrimenti — false e value conterrà il valore di default per TValue. Questo metodo non lancia eccezioni, il che lo rende molto popolare nel codice reale. |
3. Membri principali dell'interfaccia IDictionary<TKey, TValue>
E ora — la parte più importante! Vediamo cosa c'è dentro l'interfaccia IDictionary<TKey, TValue> e impariamo a usare questo "contratto" nel tuo codice.
public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>
{
TValue this[TKey key] { get; set; } // Indicizzatore per accesso tramite chiave
ICollection<TKey> Keys { get; }
ICollection<TValue> Values { get; }
void Add(TKey key, TValue value); // Aggiungi una nuova coppia (la chiave non deve esistere)
bool ContainsKey(TKey key); // Esiste questa chiave?
bool Remove(TKey key); // Rimuovi tramite chiave
bool TryGetValue(TKey key, out TValue value); // Ottieni valore in modo sicuro
}
Vediamo i punti principali:
Indicizzatore [key]
Permette di ottenere e impostare valori tramite chiave:
dictionary["Barsik"] = new Cat("Barsik", 2);
Proprietà Keys e Values
Permettono di ottenere la collezione di tutte le chiavi o di tutti i valori nel dizionario.
foreach (var name in dictionary.Keys)
{
Console.WriteLine(name);
}
Metodi Add, Remove, ContainsKey, TryGetValue
- Add(key, value) — aggiungi una nuova coppia
- Remove(key) — rimuovi tramite chiave
- ContainsKey(key) — controlla se la chiave esiste
- TryGetValue(key, out value) — ottieni valore in modo sicuro (senza eccezione se la chiave non esiste)
4. Uso di IDictionary<TKey, TValue> nella pratica
Metodi universali
Supponiamo che tu stia scrivendo una funzione che deve lavorare con un dizionario, ma non vuole sapere quale classe c'è dentro: un normale Dictionary, SortedDictionary, o magari qualcuno ha implementato il proprio "cripto-dizionario".
Dichiari il parametro come IDictionary<TKey, TValue>:
static void PrintDictionary<TKey, TValue>(IDictionary<TKey, TValue> someDictionary)
{
foreach (var pair in someDictionary)
{
Console.WriteLine($"{pair.Key}: {pair.Value}");
}
}
Ora il tuo metodo accetta qualsiasi dizionario! Sotto il cofano può esserci qualsiasi cosa — anche un dizionario che cripta i valori, se ti piace vivere pericolosamente.
Uso di IDictionary per passare parametri
Supponiamo che tu stia creando un'app per la gestione dei gatti e vuoi che il tuo metodo funzioni con tutti i possibili dizionari di impostazioni, non solo con una classe specifica. Ecco come si fa:
void SetCatParameters(IDictionary<string, string> parameters)
{
if (parameters.ContainsKey("color"))
{
Console.WriteLine($"Colora il gatto di: {parameters["color"]}");
}
}
Un'unica firma — più implementazioni
Spieghiamo con una tabella pratica:
| Classe della collezione | Implementa IDictionary<TKey,TValue> | Caratteristiche |
|---|---|---|
|
✅ Sì | Ricerca veloce, chiavi in ordine casuale |
|
✅ Sì | Le chiavi sono ordinate automaticamente |
|
✅ Sì | Le chiavi sono ordinate, più efficiente in memoria |
| (Classe personalizzata che implementa l'interfaccia) | ✅ Sì | Qualsiasi logica tu voglia, ma devi rispettare il contratto |
5. Relazione con altre interfacce di collezione
Per i veri geek: l'interfaccia IDictionary<TKey, TValue> eredita da ICollection<KeyValuePair<TKey, TValue>> e IEnumerable<KeyValuePair<TKey, TValue>>. Questo significa che qualsiasi dizionario può essere:
- Iterato con foreach sulle coppie chiave-valore,
- Aggiunto e rimosso tramite i metodi della collezione,
- Ottenere il numero di elementi.
foreach (var entry in myDictionary)
{
Console.WriteLine($"{entry.Key} => {entry.Value}");
}
6. Caratteristiche e errori tipici
Lavorare con l'indicizzatore
L'errore più comune dei principianti: tentare di ottenere un elemento tramite una chiave che non esiste genera un'eccezione KeyNotFoundException.
var value = myDictionary["NemaTakogoKlyucha"]; // Boom!
Quindi è sempre meglio usare TryGetValue:
if (myDictionary.TryGetValue("Murzik", out var cat))
{
Console.WriteLine($"Gatto trovato: {cat}");
}
else
{
Console.WriteLine($"Gatto non trovato!");
}
Aggiunta di una chiave già esistente
Se chiami Add per una chiave che già esiste, ottieni ArgumentException. Se vuoi "aggiungere o aggiornare", usa l'indicizzatore:
// Aggiunge se non esiste, aggiorna se esiste
myDictionary["Murka"] = new Cat("Murka", 5);
Iterazione e modifica
Non provare a modificare il dizionario (ad esempio, rimuovere elementi) direttamente in un ciclo foreach: otterrai un'eccezione. Se devi rimuovere qualcosa, prima raccogli la lista delle chiavi da eliminare, poi rimuovile in un ciclo separato:
// Modo sicuro per rimuovere elementi
var keysToRemove = new List<string>();
foreach (var pair in myDictionary)
{
if (pair.Value.Age > 10) // Condizione per la rimozione
{
keysToRemove.Add(pair.Key);
}
}
foreach (var key in keysToRemove)
{
myDictionary.Remove(key);
}
GO TO FULL VERSION