C# - Generics
- I Generics, permettono di progettare classi senza indicare esplicitamente i tipi di dati.
- Sono utili per esempio quando si deve costruire un metodo che possa funzionare con più tipi di dato in input e non si voglia (o non si possa) scrivere più metodi che facciano le stesse operazioni con tipi di dato differenti.
- Quando però si istanzia un oggetto da una classe che utilizza i Generics, bisogna indicarne il tipo.
- In .NET è possibile anche utilizzare il tipo ‘object’ che lascia al programmatore molta libertà nella definizione di classi generiche. L’aspetto negativo è però che il compilatore non rivela errori se nel codice sono presenti errori di conversione implicita. E, se tutto funziona, il programma è più lento a causa di dispendiose operazioni di boxing – unboxing. Con i Generics invece questi due problemi non ci sono.
- Si possono definire metodi con uno o più parametri Generic o classi che istanziano oggetti Generic: si vedano gli esempi che seguono.
- Nel caso di classi generiche è anche possibile limitare i tipi utilizzabili a quelli che implementano una o più interfacce: su questo argomento al momento non ho esempi.
- Si vuole un metodo che effettui lo scambio tra due variabili dello stesso tipo, senza indicarne il tipo.
- Una tecnica per scambiare due variabili consiste nell’utilizzare una terza variabile che conserva temporaneamente il valore di una di queste.
- Una possibile soluzione è quella di scrivere diverse versioni del metodo con diversi valori di input coerenti, per esempio Swap(int, int), Swap(string, string), e così via. Questa soluzione è ovviamente lunga e incompleta.
- Un’altra possibile soluzione è quella di scrivere la funzione con i due argomenti di tipo ‘object’: Swap(object, object). Il tutto funziona, anche se si passano due argomenti non coerenti, ma non si avrà nessun errore in fase di compilazione se i due argomenti hanno tipo diverso (cosa invece richiesta dal problema).
- Con i Generics invece si può scrivere il seguente codice.
- La classe seguente definisce il metodo generico ‘Swap’.
- Il metodo è utilizzato così:
- Se si prova ad utilizzare il metodo ‘Swap’ passando due argomenti diversi tra loro, si ottiene un errore in fase di compilazione.
- Si vuole definire una classe che lavori con oggetti di diverso tipo, per esempio ‘int’, ‘string’, eccetera.
- Quando però si definisce l’oggetto istanza della classe, occorre specificare il tipo e il compilatore indica eventuali errori nell’utilizzo dell’oggetto stesso.
- In questo esempio si definisce una classe che gestisce una lista di oggetti e ha dei metodi per inserire gli oggetti, ordinarli e scriverli in Console.
- Come si vede nella definizione della classe è presente ‘<T>’ che indica il tipo di dato che l’utente assegna all’oggetto che istanzia.
- L’utente può assegnare all’oggetto un tipo primitivo (‘int’, ‘string’, eccetera) ma anche un tipo definito dall’utente stesso (per esempio se ci fosse la classe ‘Persona’, la si può assegnare).
- Di seguito, l’utilizzo della classe con i due tipi ‘int’ e ‘string’.
List<T>
- La lista è una evoluzione dell’ArrayList, ma supporta in modo nativo l’aggiunta e l’eliminazione degli elementi. Sia la ricerca sia l’ordinamento sono operazioni molto veloci.
- Ecco alcuni esempi.
List<string> _Lista = new List<string>();
_Lista.Add("Primo elemento");
_Lista.Add("Secondo elemento");
_Lista.Add("Quarto elemento");
_Lista.Insert(2, "Terzo elemento");
// Proprietà / Metodi utili
int totElementi = _Lista.Count; // 4
string elemento = _Lista[2]; // "Terzo elemento"
int index1 = _Lista.IndexOf("Secondo elemento"); // 1
int index2 = _Lista.IndexOf("Quinto elemento"); // -1, non esiste
bool trovato = _Lista.Contains("Primo elemento"); // true
_Lista.Sort(); // la lista è ora lista ordinata
string[] array = _Lista.ToArray(); // array con gli elementi della lista
// Ricerca di un elemento con un delegate
string s = _Lista.Find(FindLength14); // "Primo elemento"
- L'struzione precedente necessita della definizione del metodo seguente:
private static bool FindLength14(string item)
{
if (item.Length == 14)
return true;
else
return false;
}
Dictionary<TKey, TValue>
- I dictionary permettono di organizzare un elenco di chiavi-valori. Le ricerche tramite chiave sono molto veloci.
- I tipi di variabili da utilizzare per le chiavi e per i valori, sono definibili dal programmatore.
- I valori utilizzati come chiave, devono essere univoci.
- I dictionary sono nel namespace System.Collections.Generic.
- Esempio con una applicazione Console.
- In questo esempio la chiave è un int, mentre il valore è string.
// Definizione del dictionary e inserimento di alcuni elementi
Dictionary<int, string> _Dic = new Dictionary<int, string>();
_Dic.Add(123, "Prima stringa");
_Dic.Add(234, "Seconda stringa");
_Dic.Add(456, "Quarta stringa");
_Dic.Add(345, "Terza stringa");
// Proprietà / Metodi utili
int totElementi = _Dic.Count; // 4
bool findKey = _Dic.ContainsKey(123); // true
bool findValue = _Dic.ContainsValue("aaa"); // false
string elemento = _Dic[123]; // "Prima stringa"
if (_Dic.TryGetValue(234, out elemento))
{ } // in 'elemento' c'è "Seconda stringa"
foreach (KeyValuePair<int, string> kv in _Dic)
{
Console.WriteLine("{0}, {1}", kv.Key, kv.Value);
}
List<int> list = new List<int>(_Dic.Keys);
list.Sort();
foreach (int item in list)
{
Console.WriteLine("{0}, {1}", item, _Dic[item]);
}
Console.ReadKey();
- Esempio con una applicazione Windows Form.
- In questo esempio la chiave è un int, mentre il valore è un oggetto (proprietà 'Nome' e 'Cognome') istanziato dalla classe seguente.
// Creazione oggetti dalla classe 'Persone'
Persone _P1 = new Persone();
_P1.Nome = "Archimede"; _P1.Cognome = "Pitagorico";
Persone _P2 = new Persone();
_P2.Nome = "Leonardo"; _P2.Cognome = "Da Vinci";
Persone _P3 = new Persone();
_P3.Nome = "Michelangelo"; _P3.Cognome = "Buonarroti";
// Creazione del dictionary
Dictionary<int, Persone> _Dic = new Dictionary<int, Persone>();
// Aggiunta, rimozione, totale elementi del dictionay
_Dic.Add(1, _P1);
_Dic.Add(2, _P2);
_Dic.Add(3, _P3);
_Dic.Remove(3);
_Dic.Remove(2);
_Dic.Remove(1);
_Dic.Clear();
int i = _Dic.Count;
// Visualizzazione di un elemento del dictionary
MessageBox.Show(_Dic[1].Nome + " " + _Dic[1].Cognome);
// Verifica se esiste una certa chiave nel dictionary
if (_Dic.ContainsKey(1))
MessageBox.Show("Esiste la chiave 1");
else
MessageBox.Show("Non esiste la chiave 1");
// Verifica se esiste un certo elemento nel dictionary
if (_Dic.ContainsValue(_P1))
MessageBox.Show("Esiste l'elemento " + _P1.Nome + " " + _P1.Cognome);
else
MessageBox.Show("Non esiste l'elemento " + _P1.Nome + " " + _P1.Cognome);
// Visualizzazione di tutti gli elementi del dictionary
foreach (KeyValuePair<int, Persone> item in _Dic)
{
MessageBox.Show(item.Key + " " + item.Value.Nome + " " + item.Value.Cognome);
}
// Preparazione di una lista con tutte le chiavi del dictionary
// Attenzione: è lento, meglio utilizzare il foreach su KeyValuePair
List<int> list = new List<int>(_Dic.Keys);
foreach (int k in list)
MessageBox.Show(k.ToString());
- Esempio per ottenere un dictionary a partire da un array o da una lista di oggetti.
- È data la classe seguente:
- Da array di oggetti a dictionary:
- Da lista di oggetti a dictionary: