Oppure

Loading

Proprietà con parametri

Nei due capitoli precedenti, ho sempre scritto proprietà che semplicemente restituivano il valore di un campo, ossia il codice del blocco Get non era nulla di più di un semplice Return. Introduciamo ora, invece, la possibilità di ottenere informazioni diverse dalla stessa proprietà specificando un parametro, proprio come fosse un metodo. Avrete notato, infatti, che fin dal principio c'era una coppia di parentesi tonde vicino al nome della proprietà, ossia proprio la sintassi che si usa per dichiarare metodi senza parametri. Ecco un esempio:
Module Module1
    'Classe che rappresenta un estrattore di numeri
    'casuali
    Class NumberExtractor
        Private _ExtractedNumbers() As Byte
        'Generatore di numeri casuali. Random è una classe
        'del namespace System
        Private Rnd As Random

        'Questa proprietà ha un parametro, Index, che
        'specifica a quale posizione dell'array si intende recarsi
        'per prelevarne il valore. Nonostante l'array abbia solo 6
        'elementi di tipo Byte, l'indice viene comunemente sempre
        'indicato come intero a 32 bit. È una specie di
        'convenzione, forse derivante dalla maggior facilità di
        'elaborazione su macchine a 32 bit
        Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
            Get
                If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
                    Return _ExtractedNumbers(Index)
                Else
                    Return 0
                End If
            End Get
        End Property

        Public Sub New()
            'Essendo di tipo reference, si deve creare un nuovo
            'oggetto Random e assegnarlo a Rnd. La ragione per cui
            'Rnd è un membro di classe consiste nel fatto
            'che se fosse stata variabile temporanea del corpo
            'della procedura ExtractNumbers, sarebbero usciti
            'gli stessi numeri. Questo perchè la sequenza
            'pseudocasuale creata dalla classe non cambia se
            'non glielo si comunica espressamente usando un altro
            'costruttore. Non tratterò questo argomento ora
            Rnd = New Random()
            ReDim _ExtractedNumbers(5)
        End Sub

        Public Sub ExtractNumbers()
            'Estrae 6 numeri casuali tra 1 e 90 e li pone nell'array
            For I As Int32 = 0 To 5
                _ExtractedNumbers(I) = Rnd.Next(1, 91)
            Next
        End Sub
    End Class

    Sub Main()
        Dim E As New NumberExtractor()

        E.ExtractNumbers()
        Console.WriteLine("Numeri estratti: ")
        For I As Int32 = 0 To 5
            Console.Write(E.ExtractedNumbers(I) & " ")
        Next

        Console.ReadKey()
    End Sub
End Module 
Notare che sarebbe stato logicamente equivalente creare una proprietà che restituisse tutto l'array, in questo modo:
Public ReadOnly Property ExtractedNumbers() As Byte()
    Get
        Return _ExtractedNumbers
    End Get
End Property 
Ma non si sarebbe avuto alcun controllo sull'indice che l'utente avrebbe potuto usare: nel primo modo, invece, è possibile controllare l'indice usato e restituire 0 qualora esso non sia coerente con i limiti dell'array. La restituzione di elementi di una lista, tuttavia, è solo una delle possibilità che le proprietà parametriche offrono, e non c'è limite all'uso che se ne può fare. Nonostante ciò, è bene sottolineare che è meglio utilizzare una funzione o una procedura (poiché le proprietà di questo tipo possono anche non essere readonly, questo era solo un caso) qualora si debbano eseguire calcoli o elaborazioni non immediati, diciamo oltre le 20/30 righe di codice, ma anche di meno, a seconda della pesantezza delle operazioni. Fate conto che le proprietà debbano sempre essere il più leggere possibile, computazionalmente parlando: qualche costrutto di controllo come If o Select, qualche calcolo sul Return, ma nulla di più.


Proprietà di default

Con questo termine si indica la proprietà predefinita di una classe. Per esistere, essa deve soddisfare questi due requisiti:
  • Deve possedere almeno un parametro;
  • Deve essere unica.
Anche se solitamente si usa in altre circostanze, ecco una proprietà di default applicata al precedente esempio:
Module Module1
Class NumberExtractor
        Private _ExtractedNumbers() As Byte
        Private Rnd As Random

        'Una proprietà di default si dichiara come una
        'normalissima proprietà, ma anteponendo allo specificatore
        'di accesso la keyword Default
        Default Public ReadOnly Property ExtractedNumbers(ByVal Index As Int32) As Byte
            Get
                If (Index >= 0) And (Index < _ExtractedNumbers.Length) Then
                    Return _ExtractedNumbers(Index)
                Else
                    Return 0
                End If
            End Get
        End Property

        Public Sub New()
            Rnd = New Random()
            ReDim _ExtractedNumbers(5)
        End Sub

        Public Sub ExtractNumbers()
            For I As Int32 = 0 To 5
                _ExtractedNumbers(I) = Rnd.Next(1, 91)
            Next
        End Sub
    End Class

    Sub Main()
        Dim E As New NumberExtractor()

        E.ExtractNumbers()
        Console.WriteLine("Numeri estratti: ")
        For I As Int32 = 0 To 5
            'Ecco l'utilità delle proprietà di default: si possono
            'richiamare anche omettendone il nome. In questo caso
            'E(I) è equivalente a scrivere E.ExtractedNumbers(I),
            'ma poiché ExtractedNumbers è di default,
            'viene desunta automaticamente
            Console.Write(E(I) & " ")
        Next

        Console.ReadKey()
    End Sub
End Module 
Dal codice salta subito all'occhio la motivazione dei due prerequisiti specificati inizialmente:
  • Se la proprietà non avesse almeno un parametro, sarebbe impossibile per il compilatore sapere quando il programmatore si sta riferendo all'oggetto e quando alla sua proprietà di default;
  • Se non fosse unica, sarebbe impossibile per il compilatore decidere quale prendere.
Le proprietà di default sono molto diffuse, specialmente nell'ambito degli oggetti windows form, ma spesso non le si sa riconoscere. Anche per quello che abbiamo imparato fin'ora, però, possiamo scovare un esempio di proprietà di default. Il tipo String espone una proprietà parametrizzata Chars(I), che permette di sapere quale carattere si trova alla posizione I nella stringa, ad esempio:
Dim S As String = "Ciao"
Dim C As Char = S.Chars(1)
' > C = "i", poiché "i" è il carattere alla posizione 1
' nella stringa S 
Ebbene, Chars è una proprietà di default, ossia è possibile scrivere:
Dim S As String = "Ciao"
Dim C As Char = S(1)
' > C = "i" 



Get e Set con specificatori di accesso

Anche se a prima vista potrebbe sembrare strano, sì, è possibile assegnare uno specificatore di accesso anche ai singoli blocchi Get e Set all'interno di una proprietà. Questa peculiare caratteristica viene sfruttata veramente poco, ma offre una grande flessibilità e un'altrettanto grande potenzialità di gestione. Limitando l'accesso ai singoli blocchi, è possibile rendere una proprietà ReadOnly solo per certe parti di codice e/o WriteOnly solo per altre parti, pur senza usare direttamente tali keywords. Ovviamente, per essere logicamente applicabili, gli specificatori di accesso dei blocchi interni devono essere più restrittivi di quello usato per contrassegnare la proprietà stessa. Infatti, se una proprietà è privata, ovviamente non potrà avere un blocco get pubblico. In genere, la gerarchia di restrittività segue questa lista, dove Public è il meno restrittivo e Private il più restrittivo:
  • Public
  • Protected Friend
  • Friend
  • Protected
  • Private
Altra condizione necessaria è che uno solo tra Get e Set può essere marcato con uno scope diverso da quello della proprietà. Non avrebbe senso, infatti, ad esempio, definire una proprietà pubblica con un Get Friend e un Set Private, poiché non sarebbe più pubblica (in quanto sia get che set non sono pubblici)! Ecco un esempio:
Public Property A() As Byte
    Get
        '...
    End Get
    Private Set(ByVal value As Byte)
        '...
    End Set
End Property 
La proprietà A è sempre leggibile, ma modificabile solo all'interno della classe che la espone. In pratica, è come una normale proprietà per il codice interno alla classe, ma come una ReadOnly per quello esterno. È pur vero che in questo caso, si sarebbe potuto renderla direttamente ReadOnly e modificare direttamente il campo da essa avvolto invece che esporre un Set privato, ma sono punti di vista. Ad ogni modo, l'uso di scope diversificati permette di fare di tutto e di più ed è solo un caso che non mi sia venuto in mente un esempio più significativo.


Mettiamo un po' d'ordine sulle keyword

In questi ultimi capitoli ho spiegato un bel po' di keyword diverse, e specialmente nelle proprietà può accadere di dover specificare molte keyword insieme. Ecco l'ordine corretto (anche se l'editor del nostro ambiente di sviluppo le riordina per noi nel caso dovessimo sbagliare):
[Default] [ReadOnly/WriteOnly] [Public/Friend/Private/...] Property ...  
E ora quelle che conoscete sono ancora poche... provate voi a scrivere una proprietà del genere:
Default Protected Friend Overridable Overloads ReadOnly Property A(ByVal Index As Int32) As Byte
    Get
        '...
    End Get
End Property 


N.B.: ovviamente, tutto quello che si è detto fin'ora sulle proprietà nelle classi vale anche per le strutture!

A cura di: Il Totem