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 PropertyOra, 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à.
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 ModulePer 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 ModuleI 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 ModuleIn 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.
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 PropertyQuando 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
[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.
A cura di: Il Totem