Questo sito utilizza cookies solo per scopi di autenticazione sul sito e nient'altro. Nessuna informazione personale viene tracciata. Leggi l'informativa sui cookies.
Username: Password: oppure
Guida al Visual Basic .NET - Le Proprieta Parte I

Guida al Visual Basic .NET

Capitolo 20° - Le Proprieta Parte I

<< Precedente Prossimo >>

Le proprietà sono una categoria di membri di classe molto importante, che useremo molto spesso da qui in avanti. Non è possibile definirne con precisione la natura: esse sono una via di mezzo tra metodi (procedure o funzioni) e campi (variabili dichiarate in una classe). In genere, si dice che le proprietà siano "campi intelligenti", poiché il loro ruolo consiste nel mediare l'interazione tra codice esterno alla classe e campo di una classe. Esse si "avvolgono" intorno a un campo (per questo motivo vengono anche chiamate wrapper, dall'inglese wrap = impacchettare) e decidono, tramite codice scritto dal programmatore, quali valori siano leciti per quel campo e quali no - stile buttafuori, per intenderci. La sintassi con cui si dichiara una proprietà è la seguente:
Property [Nome]() As [Tipo]
    Get
        '...
        Return [Valore restituito]
    End Get
    Set(ByVal value As [Tipo])
        '...
    End Set
End Property 
Ora, questa sintassi, nel suo insieme, è molto diversa da tutto ciò che abbiamo visto fino ad ora. Tuttavia, guardando bene, possiamo riconoscere alcuni blocchi di codice e ricondurli ad una categoria precedentemente spiegata:
  • La prima riga di codice ricorda la dichiarazione di una variabile;
  • Il blocco Get ricorda una funzione; il codice ivi contenuto viene eseguito quando viene richiesto il valore della proprietà;
  • Il blocco Set ricorda una procedura a un parametro; il codice ivi contenuto viene eseguito quando un codice imposta il valore della proprietà.
Da quello che ho appena scritto sembra proprio che una proprietà sia una variabile programmabile, ma allora da dove si prende il valore che essa assume? Come ho già ripetuto, una proprietà media l'interazione tra codice esterno e campo di una classe: quindi dobbiamo stabilire un modo per collegare la proprietà al campo che ci interessa. Ecco un esempio:
Module Module1
    Class Example
        'Campo pubblico di tipo Single.
        Public _Number As Single

        'La proprietà Number media, in questo caso, l'uso
        'del campo _Number.
        Public Property Number() As Single
            Get
                'Quando viene chiesto il valore di Number, viene
                'restituito il valore della variabile _Number. Si
                'vede che la proprietà non fa altro che manipolare
                'una variabile esistente e non contiene alcun
                'dato di per sé
                Return _Number
            End Get
            Set(ByVal value As Single)
                'Quando alla proprietà viene assegnato un valore,
                'essa modifica il contenuto di _Number impostandolo
                'esattamente su quel valore
                _Number = value
            End Set
        End Property
    End Class

    Sub Main()
        Dim A As New Example()

        'Il codice di Main sta impostando il valore di A.Number.
        'Notare che una proprietà si usa esattamente come una
        'comunissima variabile di istanza.
        'La proprietà, quindi, richiama il suo blocco Set come
        'una procedura e assegna il valore 20 al campo A._Number
        A.Number = 20
        
        'Nella prossima riga, invece, viene richiesto il valore
        'di Number per poterlo scrivere a schermo. La proprietà
        'esegue il blocco Get come una funzione e restituisce al
        'chiamante (ossia il metodo/oggetto che ha invocato Get,
        'in questo caso Console.WriteLine) il valore di A._Number
        Console.WriteLine(A.Number)
        
        'Per gli scettici, facciamo un controllo per vedere se
        'effettivamente il contenuto di A._Number è cambiato.
        'Potrete constatare che è uguale a 20.
        Console.WriteLine(A._Number)

        Console.ReadLine()
    End Sub
End Module 
Per prima cosa bisogna subito fare due importanti osservazioni:
  • Il nome della proprietà e quello del campo a cui essa sovrintende sono molto simili. Questa similarità viene mentenuta per l'appunto a causa dello stretto legame che lega proprietà e campo. È una convenzione che il nome di un campo mediato da una proprietà inizi con il carattere underscore ("_"), oppure con una di queste combinazioni alfanumeriche: "p_", "m_". Il nome usato per la proprietà sarà, invece, identico, ma senza l'underscore iniziale, come in questo esempio.
  • Il tipo definito per la proprietà è identico a quello usato per il campo. Abbastanza ovvio, d'altronde: se essa deve mediare l'uso di una variabile, allora anche tutti i valori ricevuti e restituiti dovranno essere compatibili.


La potenza nascosta delle proprietà

Arrivati a questo punto, uno potrebbe pensare che, dopotutto, non vale la pena di sprecare spazio per scrivere una proprietà quando può accedere direttamente al campo. Bene, se c'è veramente qualcuno che leggendo quello che ho scritto ha pensato veramente a questo, può anche andare a compiangersi in un angolino buio. XD Scherzi a parte, l'utilità c'è, ma spesso non si vede. Prima di tutto, iniziamo col dire che se un campo è mediato da una proprietà, per convenzione (ma anche per buon senso), deve essere Private, altrimenti lo si potrebbe usare indiscriminatamente senza limitazioni, il che è proprio quello che noi vogliamo impedire. A questo possiamo anche aggiungere una considerazione: visto che abbiamo la possibilità di farlo, aggiungendo del codice a Get e Set, perchè non fare qualche controllo sui valori inseriti, giusto per evitare errori peggiori in un immediato futuro? Ammettiamo di avere la nostra bella classe:
Module Module1
    'Questa classe rappresenta un semplice sistema inerziale,
    'formato da un piano orizzontale scabro (con attrito) e
    'una massa libera di muoversi su di esso
    Class InertialFrame
        Private _DynamicFrictionCoefficient As Single
        Private _Mass As Single
        Private _GravityAcceleration As Single

        'Coefficiente di attrito radente (dinamico), ?
        Public Property DynamicFrictionCoefficient() As Single
            Get
                Return _DynamicFrictionCoefficient
            End Get
            Set(ByVal value As Single)
                _DynamicFrictionCoefficient = value
            End Set
        End Property

        'Massa, m
        Public Property Mass() As Single
            Get
                Return _Mass
            End Get
            Set(ByVal value As Single)
                _Mass = value
            End Set
        End Property

        'Accelerazione di gravità che vale nel sistema, g
        Public Property GravityAcceleration() As Single
            Get
                Return _GravityAcceleration
            End Get
            Set(ByVal value As Single)
                _GravityAcceleration = value
            End Set
        End Property

        'Calcola e restituisce la forza di attrito che agisce
        'quando la massa è in moto
        Public Function CalculateFrictionForce() As Single
            Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient
        End Function

    End Class

    Sub Main()
        Dim F As New InertialFrame()

        Console.WriteLine("Sistema inerziale formato da:")
        Console.WriteLine(" - Un piano orizzontale e scabro;")
        Console.WriteLine(" - Una massa variabile.")
        Console.WriteLine()

        Console.WriteLine("Inserire i dati:")
        Console.Write("Coefficiente di attrito dinamico = ")
        F.DynamicFrictionCoefficient = Console.ReadLine
        Console.Write("Massa (Kg) = ")
        F.Mass = Console.ReadLine
        Console.Write("Accelerazione di gravità (m/s2) = ")
        F.GravityAcceleration = Console.ReadLine

        Console.WriteLine()
        Console.Write("Attrito dinamico = ")
        Console.WriteLine(F.CalculateFrictionForce() & " N")

        Console.ReadLine()
    End Sub
End Module 
I calcoli funzionano, le proprietà sono scritte in modo corretto, tutto gira alla perfezione, se non che... qualcuno trova il modo di mettere ? = 2 e m = -7, valori assurdi poiché 0 < ? <= 1 ed m > 0. Modificando il codice delle proprietà possiamo imporre questi vincoli ai valori inseribili:
Module Module1
    Class InertialFrame
        Private _DynamicFrictionCoefficient As Single
        Private _Mass As Single
        Private _GravityAcceleration As Single

        Public Property DynamicFrictionCoefficient() As Single
            Get
                Return _DynamicFrictionCoefficient
            End Get
            Set(ByVal value As Single)
                If (value > 0) And (value <= 1) Then
                    _DynamicFrictionCoefficient = value
                Else
                    Console.WriteLine(value & " non è un valore consentito!")
                    Console.WriteLine("Coefficiente attrito dinamico = 0.1")
                    _DynamicFrictionCoefficient = 0.1
                End If
            End Set
        End Property

        Public Property Mass() As Single
            Get
                Return _Mass
            End Get
            Set(ByVal value As Single)
                If value > 0 Then
                    _Mass = value
                Else
                    Console.WriteLine(value & " non è un valore consentito!")
                    Console.WriteLine("Massa = 1")
                    _Mass = 1
                End If
            End Set
        End Property

        Public Property GravityAcceleration() As Single
            Get
                Return _GravityAcceleration
            End Get
            Set(ByVal value As Single)
                _GravityAcceleration = Math.Abs(value)
            End Set
        End Property

        Public Function CalculateFrictionForce() As Single
            Return (Mass * GravityAcceleration) * DynamicFrictionCoefficient
        End Function

    End Class

    '...
End Module 
In genere, ci sono due modi di agire quando i valori che la proprietà riceve in input sono errati:
  • Modificare il campo reimpostandolo su un valore di default, ossia la strategia che abbiamo adottato per questo esempio;
  • Lanciare un'eccezione.
La soluzione formalmente più corretta sarebbe la seconda: il codice chiamante dovrebbe poi catturare e gestire tale eccezione, lasciando all'utente la possibilità di decidere cosa fare. Tuttavia, per farvi fronte, bisognerebbe introdurre ancora un po' di teoria e di sintassi, ragion per cui il suo uso è stato posto in secondo piano rispetto alla prima. Inoltre, bisognerebbe anche evitare di porre il codice che comunica all'utente l'errore nel corpo della proprietà e, più in generale, nella classe stessa, poiché questo codice potrebbe essere riutilizzato in un'altra applicazione che magari non usa la console (altra ragione per scegliere la seconda possibilità). Mettendo da parte tali osservazioni di circostanza, comunque, si nota come l'uso delle proprietà offra molta più gestibilità e flessibilità di un semplice campo. E non è ancora finita...


Curiosità: dietro le quinte di una proprietà

N.B.: Potete anche procedere a leggere il prossimo capitolo, poiché questo paragrafo è puramente illustrativo.
Come esempio userò questa proprietà:
Property Number() As Single
    Get
        Return _Number
    End Get
    Set(ByVal value As Single)
        If (value > 30) And (value < 100) Then
            _Number = value
        Else
            _Number = 31
        End If
    End Set
End Property 
Quando una proprietà viene dichiarata, ci sembra che essa esista come un'entità unica nel codice, ed è più o meno vero. Tuttavia, una volta che il sorgente passa nelle fauci del compilatore, succede una cosa abbastanza singolare. La proprietà cessa di esistere e viene invece spezzata in due elementi distinti:
  • Una funzione senza parametri, di nome "get_[Nome Proprietà]", il cui corpo viene creato copiando il codice contenuto nel blocco Get. Nel nostro caso, get_Number:
    Function get_Number() As Single
        Return _Number
    End Function 
    
  • Una procedura con un parametro, di nome "set_[Nome Proprietà]", il cui corpo viene creato copiando il codice contenuto nel blocco Set. Nel nostro caso, set_Number:
    Sub set_Number(ByVal value As Single)
        If (value > 30) And (value < 100) Then
            _Number = value
        Else
            _Number = 31
        End If
    End Sub 
    
Entrambi i metodi hanno come specificatore di accesso lo stesso della proprietà. Inoltre, ogni riga di codice del tipo
[Proprietà] = [Valore] 
oppure
[Valore] = [Proprietà] 
viene sostituita con la corrispondente riga:
set_[Nome Proprietà]([Valore]) 
oppure:
[Valore] = get_[Nome Proprietà] 
Ad esempio, il seguente codice:
Dim A As New Example
A.Number = 20
Console.WriteLine(A.Number) 
viene trasformato, durante la compilazione, in:
Dim A As New Example
A.set_Number(20)
Console.WriteLine(A.get_Number()) 
Questo per dire che una proprietà è un costrutto di alto livello, uno strumento usato nella programmazione astratta: esso viene scomposto nelle sue parti fondamentali quando il programma passa al livello medio, ossia quando è tradotto in IL, lo pseudo-linguaggio macchina del Framework .NET.

<< Precedente Prossimo >>
A proposito dell'autore

C#, TypeScript, java, php, EcmaScript (JavaScript), Spring, Hibernate, React, SASS/LESS, jade, python, scikit, node.js, redux, postgres, keras, kubernetes, docker, hexo, etc...