Proprietà ReadOnly e WriteOnly
Fin'ora abbiamo visto che le proprietà sono in grado di mediare l'interazione tra codice esterno alla classe e suoi campi, e tale mediazione comprendeva la possibilità di rifiutare certi valori e consentirne altri. Ma non è finita qui: usando delle apposite keywords è possibile rendere una proprietà a sola lettura (ossia è possibile leggerne il valore ma non modificarlo) o a sola scrittura (ossia è possibile modificarne il valore ma non ottenerlo). Per quanto riguarda la prima, viene abbastanza naturale pensare che ci possano essere valori solo esposti verso cui è proibita la manipolazione diretta, magari perché particolarmente importanti o, più spesso, perchè logicamente immutabili (vedi oltre per un esempio); spostando l'attenzione per un attimo sulla seconda, però, sarà parimenti del tutto lecito domandarsi quale sia la loro utilità. Le variabili, i campi, e quindi, per estensione, anche le proprietà, sono per loro natura atti a contenere dati, che verranno poi utilizzati in altre parti del programma: tali dati vengono continuamente letti e/o modificati e, per quanto sia possibile credere che ve ne siano di immodificabili, come costanti e valori a sola lettura, appare invece assurda l'esistenza di campi solo modificabili. Per modificare qualcosa, infatti, se ne deve conoscere almeno qualche informazione. La realtà è che le proprietà WriteOnly sono innaturali per la stragrande maggiorandza dei programmatori; piuttosto di usarle è meglio definire procedure. Mi occuperò quindi di trattare solo la keyword ReadOnly.In breve, la sintassi di una proprietà a sola lettura è questa:
ReadOnly Property [Nome]() As [Tipo] Get '... Return [Valore] End Get End PropertyNotate che il blocco Set è assente: ovviamente, si tratta di codice inutile dato che la proprietà non può essere modificata. Per continuare il discorso iniziato prima, ci sono principalmente tre motivi per dichiarare un'entità del genere:
- I dati a cui essa fornisce accesso sono importanti per la vita della classe, ed è quindi necessario lasciare che la modifica avvenga tramite altri metodi della classe stessa. Tuttavia, non c'è motivo di nasconderne il valore al codice esterno, cosa che può anche rivelarsi molto utile, sia come dato da elaborare, sia come informazione di dettaglio;
- La proprietà esprime un valore che non si può modificare perchè per propria natura immutabile. Un classico esempio può essere la data di nascita di una persona: tipicamente la si inserisce come parametro del costruttore, o la si preleva da un database, e viene memorizzata in un campo esposto tramite proprietà ReadOnly. Questo è logico, poiché non si può cambiare la data di nascita; è quella e basta. Un caso particolare sarebbe quello di un errore commesso durante l'inserimento della data, che costringerebbe a cambiarla. In questi casi, la modifica avviene per altre vie (metodi con autenticazione o modifica del database);
- La proprietà esprime un valore che viene calcolato al momento. Questo caso è molto speciale, poiché va al di là
della normale funzione di wrapper che le proprietà svolgono normalmente. Infatti, si può anche scrivere una proprietà
che non sovrintende ad alcun campo, ma che, anzi, crea un campo fittizio: ossia, da fuori sembra che ci sia un'informazione in più
nella classe, ma questa viene solo desunta o interpolata da altri dati noti. Esempio:
Class Cube Private _SideLength As Single Private _Density As Single Public Property SideLength() As Single Get Return _SideLength End Get Set(ByVal value As Single) If value > 0 Then _SideLength = value Else _SideLength = 1 End If End Set End Property Public Property Density() As Single Get Return _Density End Get Set(ByVal value As Single) If value > 0 Then _Density = value Else _Density = 1 End If End Set End Property Public ReadOnly Property SurfaceArea() As Single Get Return (SideLength ^ 2) End Get End Property Public ReadOnly Property Volume() As Single Get Return (SideLength ^ 3) End Get End Property Public ReadOnly Property Mass() As Single Get Return (Volume * Density) End Get End Property End Class
Vedendola dall'esterno, si può pensare che la classe Cube contenga come dati concreti (variabili) SideLength, Density, SurfaceArea, Volume e Mass, e che questi siano esposti tramite una proprietà. In realtà essa ne contiene solo i primi due e in base a questi calcola gli altri.
Module Esempio3 Class LogFile Private _FileName As String Private _CreationTime As Date 'Niente deve modificare il nome del file, altrimenti 'potrebbero verificarsi errori nella lettura o scrittura 'dello stesso, oppure si potrebbe chiudere un file 'che non esiste ancora Public ReadOnly Property FileName() As String Get Return _FileName End Get End Property 'Allo stesso modo non si pu? modificare la data di 'creazione di un file: una volta creato, viene 'prelevata l'ora e il giorno e impostata la 'variabile. Se potesse essere modificata 'non avrebbe più alcun significato Public ReadOnly Property CreationTime() As Date Get Return _CreationTime End Get End Property Public Sub New(ByVal Path As String) _FileName = Path _CreationTime = Date.Now End Sub End Class End Module
Una nota sui tipi reference
C'è ancora un'ultima, ma importante, clausola da far notare per le proprietà ReadOnly. Si è gi? vista la differenza tra i tipi value e i tipi reference: i primi contengono un valore, mentre i secondi un puntatore all'area di memoria in cui risiede l'oggetto voluto. A causa di questa particolare struttura, leggere il valore di un tipo reference da una proprietà ReadOnly significa saperne l'indirizzo, il che equivale ad ottenere il valore dell'oggetto puntato. Non è quindi assolutamente sbagliato scrivere:Class ASystem Private _Box As Cube Public ReadOnly Property Box() As Cube Get Return _Box End Get End Property '... End Class '... Dim S As New ASystem() S.Box.SideLength = 4In questo modo, noi staimo effettivamente modificando l'oggetto S.Box, ma indirettamente: non stiamo, invece, cambiando il valore del campo S._Box, che effettivamente è ciò che ci viene impedito di fare. In sostanza, non stiamo assegnando un nuovo oggetto alla variabile S._Box, ma stiamo solo manipolando i dati di un oggetto esistente, e questo è consentito. Anzi, è molto meglio dichiarare proprietà di tipo reference come ReadOnly quando non è necessario assegnare o impostare nuovi oggetti.
A cura di: Il Totem