I tipi enumeratori o enum sono utili quando una variabile all'interno di un programma può assumere solo un insieme di valori prestabiliti. Pensiamo a situazioni reali di utilizzo come un controllo web che può avere solo uno tra cinque colori oppure un utente a cui può essere assegnato solamente un ruolo tra i quattro disponibili.
Definire un enumerato
Quando si dichiara un enumerato si specifica un insieme di valori possibili per l'instanza dell'enumerazione; ai valori si possono assegnare dei nomi descrittivi. Per capire come sia fatto un enumerato proviamo a vederne uno:
/*
Nel framework esiste già un enumerato che si occupa
di definire i giorni della settimana, System.DayOfWeek;
a scopo illustrativo viene comodo riprenderlo a modo nostro.
*/
//Definisco il mio Enumerato
public enum GiorniDellaSettimana
{
Lunedì,
Martedì,
Mercoledì,
Giovedì,
Venerdì,
Sabato,
Domenica
}
//Proviamo ad usarlo
GiorniDellaSettimana giorno = (GiorniDellaSettimana) 2;
Console.WriteLine("Oggi è {0}",giorno);
Console.WriteLine("Oggi è {0}",GiorniDellaSettimana.Mercoledì);
/*
Il risultato, in entrambi i casi, sarà:
Oggi è Mercoledì
Oggi è Mercoledì
*/
GiorniDellaSettimana è un enumerato che utilizza valori interi per rappresentare i giorni che compongono una settimana. I valori sono assegnati automaticamente dal compilatore e vanno da 0 (Lunedì) a 6 (Domenica). Per comportamento predefinito il valore del primo membro pari a 1 e successivamente viene incrementato di una unità per ogni membro successivo.
L'accesso ai membri dell'enumerato diventa possibile tramite l'enumerazione, per esempio GiorniDellaSettimana.Lunedì restituisce il valore 0.
Questo non è il solo comportamento possibile, infatti possiamo associare un valore specifico per uno o più membri.
public enum GiorniDellaSettimana
{
Lunedì = 1,
Martedì = 2
,
Mercoledì =
3,
Giovedì = 4,
Venerdì,
Sabato,
Domenica
}
Non è obbligatorio rispettare una sequenza specifica nell'assegnazione dei valori ai membri potremmo tranquillamente avere una situazione simile:
//Valido, ma sicuramente meno logico
public enum GiorniDellaSettimana
{
Lunedì = 100,
Martedì = 10,
Mercoledì = 20,
Giovedì = 30,
Venerdì = 40,
Sabato = 50,
Domenica = 60
}
Possiamo sia assegnare valori identici a più membri dello stesso enumerato che utilizzare valori calcolati partendo dai valori di altri membri dell'enumerato, l'importante è evitare riferimenti circolari.
public enum GiorniDellaSettimana
{
Lunedì = 10,
Martedì = 10 + 30,
Mercoledì = 20,
Giovedì = 30,
Venerdì = Martedì + Lunedì,
Sabato = 2,
Domenica = 2
}
Se tentiamo di assegnare valori non leciti, ad esempio un valore intero che non sia compreso nella lista dei membri, il compilatore genera un'eccezione aiutandoci a capire dove stiamo sbagliando.
I tipi base per un enumerato
Gli enumerati sono tipi interi definiti dall'utente, ogni enumerato ha un tipo sottostante (detti tipi base) che specifica lo spazio di memoria occupato da tale enumerato. I tipi base ammessi sono: byte, sbyte, short, ushort, int, uint, long o ulong.
Il tipo base viene specificato nella dichiarazione dell'enumerato dopo il suo nome, se omettiamo il tipo base per impostazione predefinita viene scelto il tipo base int.
public enum GiorniDellaSettimana: byte
{
Lunedì,
Martedì,
Mercoledì,
Giovedì,
Venerdì,
Sabato,
Domenica
}
Per convertire un membro di un enumerato nel tipo dati sottostante da cui è composto è necessario effettuare una conversione esplicita:
//Int32 num = GiorniDellaSettimana.Domenica; //Non funziona in C#
Int32 num = (Int32)GiorniDellaSettimana.Domenica; //OK, è valido in C#
I vantaggi nell'uso degli enumerati
Una volta compilato il codice possiamo accedere ai membri dell'enumerato in modo visuale, questo negli editor che supportano la tecnologia Intellisense, semplificando il compito dello sviluppatore nello scegliere i valori appropriati.
Scrivere utilizzando gli enumerati offre tre grossi vantaggi:
- Semplifica la manutenzione del codice, perché viene controllata l'assegnazione di valori appropriati e predefiniti alle variabili.
- Migliora la leggibilità del codice, in quanto facciamo riferimento in modo testuale a valori interi, rendendo in questo modo il codice "auto-documentante".
- Allegerisce il lavoro di digitazione del programmatore grazie all'uso dell'Intellisense.
Microsoft stessa, scrivendo il Framework .NET, ha fatto uso abbondante delle enumerazioni; pensiamo all'enumerato ListItemType che troviamo nei controlli DataGrid, Datalist e Repeater.
Quando usare gli Enumerati
Tipicamente vengono utilizzati per passare valori approriati a metodi e proprietà di oggetti. Successivamente iterando tra i valori possibili con dichiarazioni di tipo switch vengono effettuate specifiche operazioni. Ecco un esempio di un possibile utilizzo di un enumerato all'interno di un metodo:
public class DemoEnum
{
public DemoEnum()
{
}
/*
Scrivere la routine avendo a disposizione 1,2,3,4,5,6 sarebbe
piuttosto "scarno"...
*/
public void FaiQualcosa(GiorniDellaSettimana giorno)
{
switch(giorno)
{
case GiorniDellaSettimana.Lunedì:
//Si comincia a lavorare...
break;
case GiorniDellaSettimana.Martedì:
//Si lavora...
break;
case GiorniDellaSettimana.Mercoledì:
//Si lavora...
break;
default:
//Ci si riposa... per 4 giorni...
break;
}
}
}
Usare la classe System.Enum
Gli enumerati come tipi ereditano le loro caratteristiche di base dalla classe System.Enum, la quale contiene anche numerosi metodì di utilità che ci permettono di rendere produttivo il codice scritto con gli enumerati.
Convertire una stringa in un membro di un enumerato
Una situazione tipica è quella di convertire una stringa in un membro di un enumerato; ad esempio convertire "lunedì" in GiorniDellaSettimana.Lunedì.
Utilizzando la classe System.Enum, ci serviamo del metodo Parse per effettuare questa conversione.
GiorniDellaSettimana giorno = (GiorniDellaSettimana)
(Enum.Parse(typeof(GiorniDellaSettimana),"lunedì",true));
Leggere il contenuto di un enumerato
Per esigenze di programmazione può far comodo accedere in modo programmatico ai membri di un enumerato. Supponiamo di voler popolare una ListBox con tutti i nomi dei valori contenuti nell'enumerato GiorniDellaSettimana.
Utilizzando la classe System.Enum ci serviamo del metodo GetValues(Type enumType) per ottenere un array con tutti i valori dell'enumerato.
foreach(GiorniDellaSettimana giorno in Enum.GetValues(typeof(GiorniDellaSettimana)))
{
DropDownList1.Items.Add(giorno);
//Posso anche recuperare il valore numerico abbinato al giorno letto
Int32 valNumGiorno = (int)giorno;
}
A questo punto ogni elemento della ListBox contiene una istanza regolare di GiorniDellaSettimana che possiamo recuperare tramite il metodo DropDownList1.SelectedItem.
Nella classe System.Enum esiste anche un metodo GetNames(Type enumType) che restituisce un array contenente i nomi delle costanti dell'enumerato.
foreach(string s in Enum.GetNames(typeof(GiorniDellaSettimana)))
Console.WriteLine(s);
Cercare un membro di un enumerato con uno specifico valore
Oltre a scorrere il contenuto di un enumerato è utile poter cercare un valore testuale contenuto nell'enumerato sulla base del valore numerico ad esso associato. Qual è il nome del settimo giorno della settimana?
Utilizzando la classe System.Enum ci serviamo del metodo GetName(Type enumType, object value) per ottentere il nome della costante abbinato al valore specificato:
Console.WriteLine("Il 7° giorno della settimana è {0}",Enum.GetName(typeof(GiorniDellaSettimana),7));
Decorare con descrizioni testuali un enumerato
Grazie al Framework .NET possiamo arricchire e decorare con attributi il codice che scriviamo. Gli enumerati come molti altri oggetti in C# accettano l'uso di attributi personalizzati. Per lavorare con gli attributi dobbiamo utilizzare il namespace System.Reflection. Rivediamo l'enumerato GiorniDellaSettimana decorato con l'attributo Description preso in prestito dalla classe System.ComponentModel.DescriptionAttribute. Ecco l'esempio:
using System;
using System.ComponentModel;
using System.Reflection;
public enum GiorniDellaSettimana
{
[Description("Che brutto è Lunedì")] Lunedì = 1,
[Description("Che brutto è Martedì")] Martedì = 2,
[Description("Che brutto è Mercoledì")] Mercoledì= 3,
[Description("Che brutto è Giovedì")] Giovedì= 4,
[Description("Che brutto è Venerdì")] Venerdì= 5,
[Description("Che bello è Sabato")] Sabato= 6,
[Description("Che brutto domani sarà Lunedì")] Domenica = 7
}
Ora che abbiamo decorato l'enumerato con del testo descrittivo potrebbe tornarci comodo volerle visualizzare nelle nostra applicazione. Niente di più semplice, grazie ai metadati e all'uso del namespace System.Reflection, voilà:
public static string DammiDescrizioneEnum(Enum valore)
{
FieldInfo fi= valore.GetType().GetField(valore.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[]) fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
return (attributes.Length>0)?attributes[0].Description: valore.ToString();
}
Usare un Enumerato come un set Flags
Una delle cose che forse non tutti sanno e che un enumerato può essere trattato come set di flags. Vediamo un esempio:
[Flags]
private enum Pulsanti
{
Ok = 1,
Cancella = 2,
Riprova = 4,
Aiuto = 8,
Tutto = Ok | Cancella | Riprova | Aiuto
}
La prima cosa che balza all'occhio è che nella dichiarazione dell'enumerato abbiamo aggiunto un attributo di nome Flags, questo è fondamentale per far capire al Framework .NET che questo enumerato deve essere trattamo come un set di flags.
La seconda è che dobbiamo assegnare un valore preciso per ogni membro dell'enumerato. Il primo membro ha valore pari a uno, gli altri avranno un valore doppio rispetto ai precedenti, quindi 1,2,4,8 ecc. Altra cosa interessante è il membro Tutto, come vedete contiene tutti i valori precedenti!
Le limitazioni degli Enumerati
Ci sono ovviamente delle restrizioni di cui dobbiamo tener conto quando lavoriamo con gli enumerati:
- Non possono definire propri metodi
- Non possono implemetare interfacce
- Non possono definire proprietà e indicizzatori
In conclusione vorrei segnalare che una demo che illustra le tematiche descritte nell'articolo potete trovarla qui.