Gli enumeratori sono tipi value particolari, che permettono di raggruppare sotto un unico nome più costanti. Essi vengono utilizzati soprattutto per rappresentare opzioni, attributi, caratteristiche o valori predefiniti, o, più in generale, qualsiasi dato che si possa "scegliere" in un insieme finito di possibilità. Alcuni esempi di enumeratore potrebbero essere lo stato di un computer (acceso, spento, standby, ibernazione, ...) o magari gli attributi di un file (nascosto, archivio, di sistema, sola lettura, ...): non a caso, per quest'ultimo, il .NET impiega veramente un enumeratore. Ma prima di andare oltre, ecco la sintassi da usare nella dichiarazione:
Enum [Nome] [Nome valore 1] [Nome valore 2] ... End EnumAd esempio:
Module Module1 'A seconda di come sono configurati i suoi caratteri, una 'stringa può possedere diverse denominazioni, chiamate 'Case. Se è costituita solo da caratteri minuscoli '(es.: "stringa di esempio") si dice che è in Lower 'Case; al contrario se contiene solo maiuscole (es.: "STRINGA 'DI ESEMPIO") sarà Upper Case. Se, invece, ogni 'parola ha l'iniziale maiuscola e tutte le altre lettere 'minuscole si indica con Proper Case (es.: "Stringa Di Esempio"). 'In ultimo, se solo la prima parola ha l'iniziale 'maiuscola e il resto della stringa è tutto minuscolo 'e questa termina con un punto, si ha Sentence Case '(es.: "Stringa di esempio."). 'Questo enumeratore indica questi casi Enum StringCase Lower Upper Sentence Proper End Enum 'Questa funzione converte una stringa in uno dei Case 'disponibili, indicati dall'enumeratore. Il secondo parametro 'è specificato fra parentesi quadre solamente perchè 'Case è una keyword, ma noi la vogliamo usare come 'identificatore. Function ToCase(ByVal Str As String, ByVal [Case] As StringCase) As String 'Le funzioni per convertire in Lower e Upper 'case sono già definite. E' sufficiente 'indicare un punto dopo il nome della variabile 'stringa, seguito a ToLower e ToUpper Select Case [Case] Case StringCase.Lower Return Str.ToLower() Case StringCase.Upper Return Str.ToUpper() Case StringCase.Proper 'Consideriamo la stringa come array di 'caratteri: Dim Chars() As Char = Str.ToLower() 'Iteriamo lungo tutta la lunghezza della 'stringa, dove Str.Length restituisce appunto 'tale lunghezza For I As Int32 = 0 To Str.Length - 1 'Se questo carattere è uno spazio oppure 'è il primo di tutta la stringa, il 'prossimo indicherà l'inizio di una nuova 'parola e dovrà essere maiuscolo. If I = 0 Then Chars(I) = Char.ToUpper(Chars(I)) End If If Chars(I) = " " And I < Str.Length - 1 Then 'Char.ToUpper rende maiuscolo un carattere 'passato come parametro e lo restituisce Chars(I + 1) = Char.ToUpper(Chars(I + 1)) End If Next 'Restituisce l'array modificato (un array di caratteri 'e una stringa sono equivalenti) Return Chars Case StringCase.Sentence 'Riduce tutta la stringa a Lower Case Str = Str.ToLower() 'Imposta il primo carattere come maiuscolo Dim Chars() As Char = Str Chars(0) = Char.ToUpper(Chars(0)) Str = Chars 'La chiude con un punto Str = Str & "." Return Str End Select End Function Sub Main() Dim Str As String = "QuEstA ? una stRingA DI prova" 'Per usare i valori di un enumeratore bisogna sempre scrivere 'il nome dell'enumeratore seguito dal punto Console.WriteLine(ToCase(Str, StringCase.Lower)) Console.WriteLine(ToCase(Str, StringCase.Upper)) Console.WriteLine(ToCase(Str, StringCase.Proper)) Console.WriteLine(ToCase(Str, StringCase.Sentence)) Console.ReadKey() End Sub End ModuleL'enumeratore StringCase offre quattro possibilità: Lower, Upper, Proper e Sentence. Chi usa la funzione è invitato a scegliere una fra queste costanti, ed in questo modo non si rischia di dimenticare il significato di un codice. Notare che ho scritto "invitato", ma non "obbligato", poichè l'Enumeratore è soltanto un mezzo attraverso il quale il programmatore dà nomi significativi a costanti, che sono pur sempre dei numeri. A prima vista non si direbbe, vedendo la dichiarazione, ma ad ogni nome indicato come campo dell'enumeratore viene associato un numero (sempre intero e di solito a 32 bit). Per sapere quale valore ciascun identificatore indica, basta scrivere un codice di prova come questo:
Console.WriteLine(StringCase.Lower) Console.WriteLine(StringCase.Upper) Console.WriteLine(StringCase.Sentence) Console.WriteLine(StringCase.Proper)A schermo apparirà
0 1 2 3Come si vede, le costanti assegnate partono da 0 per il primo campo e vengono incrementate di 1 via via che si procede a indicare nuovi campi. È anche possibile determinare esplicitamente il valore di ogni identificatore:
Enum StringCase Lower = 5 Upper = 10 Sentence = 20 Proper = 40 End EnumSe ad un nome non viene assegnato valore, esso assumerà il valore del suo precedente, aumentato di 1:
Enum StringCase Lower = 5 Upper '= 6 Sentence = 20 Proper '= 21 End EnumGli enumeratori possono assumere solo valori interi, e sono, a dir la verità, direttamente derivati dai tipi numerici di base. È, infatti, perfettamente lecito usare una costante numerica al posto di un enumeratore e viceversa. Ecco un esempio lampante in cui utilizzo un enumeratore indicante le note musicali da cui ricavo la frequenza delle suddette:
Module Module1 'Usa i nomi inglesi delle note. L'enumerazione inizia 'da -9 poiché il Do centrale si trova 9 semitoni 'sotto il La centrale Enum Note C = -9 CSharp D DSharp E F FSharp G GSharp A ASharp B End Enum 'Restituisce la frequenza di una nota. N, in concreto, 'rappresenta la differenza, in semitoni, di quella nota 'dal La centrale. Ecco l'utilittà degli enumeratori, 'che danno un nome reale a ciò che un dato indica 'indirettamente Function GetFrequency(ByVal N As Note) As Single Return 440 * 2 ^ (N / 12) End Function 'Per ora prendete per buona questa funzione che restituisce 'il nome della costante di un enumeratore a partire dal 'suo valore. Avremo modo di approfondire nei capitoli 'sulla Reflection Function GetName(ByVal N As Note) As String Return [Enum].GetName(GetType(Note), N) End Function Sub Main() 'Possiamo anche iterare usando gli enumeratori, poiché 'si tratta pur sempre di semplici numeri For I As Int32 = Note.C To Note.B Console.WriteLine("La nota " & GetName(I) & _ " risuona a una frequenza di " & GetFrequency(I) & "Hz") Next Console.ReadKey() End Sub End ModuleÈ anche possibile specificare il tipo di intero di un enumeratore (se Byte, Int16, Int32, Int64 o SByte, UInt16, UInt32, UInt64) apponendo dopo il nome la clausola As seguita dal tipo:
Enum StringCase As Byte Lower = 5 Upper = 10 Sentence = 20 Proper = 40 End EnumQuesta particolarità si rivela molto utile quando bisogna scrivere enumeratori su file in modalità binaria. In questi casi, essi rappresentano solitamente un campo detto Flags, di cui mi occuperò nel prossimo paragrafo.
Campi codificati a bit (Flags)
Chi non conosca il codice binario può leggere un articolo su di esso qui.I campi codificati a bit sono enumeratori che permettono di immagazzinare numerose informazioni in pochissimo spazio, anche in un solo byte! Di solito, tuttavia, si utilizzano tipi Int32 perchè si ha bisogno di un numero maggiore di informazioni. Il meccanismo è molto semplice. Ogni opzione deve poter assumere due valori, Vero o Falso: questi vengono quindi codificati da un solo bit (0 o 1), ad esempio:
00001101Rappresenta un intero senza segno a un byte, ossia il tipo Byte: in esso si possono immagazzinare 8 campi (uno per ogni bit), ognuno dei quali può essere acceso o spento. In questo caso, sono attivi solo il primo, il terzo e il quarto valore. Per portare a termine con successo le operazioni con enumeratori progettati per codificare a bit, è necessario che ogni valore dell'enumeratore sia una potenza di 2, da 0 fino al numero che ci interessa. Il motivo è molto semplice: dato che ogni potenza di due occupa un singolo spazio nel byte, non c'è pericolo che alcuna opzione si sovrapponga. Per unire insieme più opzioni bisogna usare l'operatore logico Or. Un esempio:
Module Module1 'È convenzione che gli enumeratori che codificano a bit 'abbiano un nome al plurale 'Questo enumeratore definisce alcuni tipi di file Public Enum FileAttributes As Byte '1 = 2 ^ 0 'In binario: '00000001 Normal = 1 '2 = 2 ^ 1 '00000010 Hidden = 2 '4 = 2 ^ 2 '00000100 System = 4 '8 = 2 ^ 3 '00001000 Archive = 8 End Enum Sub Main() Dim F As FileAttributes 'F all'inizio è 0, non contiene niente: '00000000 F = FileAttributes.Normal 'Ora F è 1, ossia Normal '00000001 F = FileAttributes.Hidden Or FileAttributes.System 'La situazione diventa complessa: 'Il primo valore è 2: 000000010 'Il secondo valore è 4: 000000100 'Abbiamo già visto l'operatore Or: restituisce True se 'almeno una delle condizioni è vera: qui True è '1 e False è 0: '000000010 Or '000000100 = '000000110 'Come si vede, ora ci sono due campi attivi: 4 e 2, che 'corrispondono a Hidden e System. Abbiamo fuso insieme due 'attributi con Or F = FileAttributes.Archive Or FileAttributes.System Or _ FileAttributes.Hidden 'La stessa cosa: '00001000 Or '00000100 Or '00000010 = '00001110 End Sub End ModuleOra sappiamo come immagazzinare i campi, ma come si fa a leggerli? Nel procedimento inverso si una invece un And:
Module Module1 Sub Main() Dim F As FileAttributes F = FileAttributes.Archive Or FileAttributes.System Or _ FileAttributes.Hidden 'Ora F è 00001110 e bisogna eseguire un'operazione di And 'sui bit, confrontando questo valore con Archive, che è 8. 'And restituisce Vero solo quando entrambe le condizioni 'sono vere: '00001110 And '00001000 = '00001000, ossia Archive! If F And FileAttributes.Archive = FileAttributes.Archive Then Console.WriteLine("Il file è marcato come 'Archive'") End If Console.ReadKey() End Sub End ModuleIn definitiva, per immagazzinare più dati in poco spazio occorre un enumeratore contenente solo valori che sono potenze di due; con Or si uniscono più campi; con And si verifica che un campo sia attivo.
A cura di: Il Totem