|
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 Enum
Ad 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 Module
L'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
3
Come 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 Enum
Se 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 Enum
Gli 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 Enum
Questa 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:
00001101
Rappresenta 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 Module
Ora 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 Module
In 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.
|
|