Oppure

Loading

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.

A cura di: Il Totem