Le classi in VB.NET - Parte 1
a cura di Stefano Castelli (Livello: Principianti)

Premessa
La serie di articoli che compariranno con la mia firma su VB-T&T hanno lo scopo (o almeno la speranza) di guidare un programmatore con una buona esperienza di Visual Basic 6 nel nuovo linguaggio Visual Basic NET.

Il criterio che ho deciso di adottare nello scrivere questi articoli, è piuttosto semplice: ogni volta tratterò un argomento, o parte di esso (ad es. parlare delle classi in un singolo articolo sarebbe troppo gravoso sia da scrivere che da leggere) in maniera da riuscire a fare in Visual Basic NET le stesse cose che sapevamo fare con VB 6 e successivamente vedere quali sono le novità.

Nel caso di Visual Basic NET, l'aggettivo nuovo è assolutamente appropriato. I cambiamenti apportati a VB non sono solo estetici, ma soprattutto sostanziali. E non sto parlando solo dell'IDE, che pure ha avuto tanti ed importanti miglioramenti, ma del cuore del linguaggio stesso. Tant'è che MS ha ritenuto di dover fornire con la versione finale di Visual Studio .NET un "wizard" il cui scopo è di facilitare la migrazione dei progetti esistenti verso il nuovo ambiente - unica ragione che può spiegare il mantenimento di On Error Goto. Per dirla con William Vaughn, i cambiamenti e le rotture con il passato sono così tante che invece di Visual Basic avrebbero potuto chiamarlo Visual Fred. Come in tutti cambiamenti, qualcosa si perde e qualcosa si guadagna. Ciò che guadagnamo è un linguaggio la cui potenza ed eleganza non è nemmeno lontanamente paragonabile a quella che avevamo in Visual Basic 6. Questa potenza la paghiamo con la necessità di imparare (tanto per cambiare!) un insieme di regole tutto nuovo.

Il primo di questi articoli tratterà i cambiamenti avvenuti nelle classi.

Classi
In Visual Basic NET le classi hanno subito una grande evoluzione. E non poteva essere altrimenti, dato che adesso ogni cosa, anche una variabile, è un oggetto (vedi nota 1).

Quando si crea una nuova classe, Visual Basic NET crea un nuovo file. La prima differenza con VB 6 è l'estensione di questo file. Non più .cls ma .vb, come del resto ogni file sorgente creato con Visual Basic NET. Infatti, a differenza di quanto avviene in VB 6, l'IDE riconosce se un file contiene una classe, o un form o un altro tipo di oggetto non dall'estensione ma dall'intestazione. Quindi l'IDE inserisce automaticamente il seguente codice nel nuovo file

  Public Class Class1
End Class

Questo identifica il contenuto del file come classe, e tutto il resto del codice deve essere scritto all'interno di queste due righe.

Creare una proprietà.
La maniera di scrivere le proprietà alla quale eravamo abituati in VB 6 è ormai storia passata. Infatti non esistono più due differenti procedure, una per Property Get e l'altra per Property Let/Set, ma solo una singola procedura che serve entrambi gli scopi. Inoltre, il costrutto Let è stato eliminato: si usa solo Set (adesso ogni cosa è un oggetto, ricordate?).

Fortunatamente l'IDE di Visual Basic NET ci viene in aiuto e crea per noi lo scheletro della proprietà. Dopo che si ha digitato Public Property seguito dal nome e dal tipo, come ad esempio

Public Property Name() As String

basta premere il tasto <INVIO> e Visual Basic NET crea la seguente struttura:

  Public Property Name() As String
Get
End Get
Set(ByVal Value As String)
End Set
End Property

Come si vede, all'interno della procedura ci sono due sezioni; una per la parte Get ed una per la parte Set. Queste possono essere pensate come la coppia di funzioni Property Get/Let di VB 6, tant'è che non è possibile inserire una dichiarazione di variabile al di fuori di una di queste due sezioni. Quindi il seguente codice è scorretto!

  Public Property Name() As String
Dim a As Integer 'dichiarazione non consentita qui
Get Return mName
End Get
Set(ByVal Value As String)
mName = Value
End Set
End Property

Ovviamente è possibile cambiare il nome del parametro Value nella parte Set.

Proprietà ReadOnly/WriteOnly
Si possono creare proprieta Read Only e Write Only, specificandolo esplicitamente nella dichiarazione

  Private ReadOnly Property Age() As Byte

Get

End Get

End Property

Visual Basic NET non accetta una proprietà con la sola parte Get senza il modificatore ReadOnly nella dichiarazione della proprietà. Lo stesso vale per proprieta WriteOnly.

Restituire i valori - Return
Nella parte Get della proprietà in genere si restituisce lo stato della proprietà stessa. In VB 6 l'unica maniera di restituire i valori è quella di usare un'istruzione del tipo:

NomeProprieta = Valore

In Visual Basic NET si può usare la parola chiave Return:

  Public Property Name() As String
Get
Return mName
End Get

Set(ByVal Value As String)
...
End Set
End Property

Infatti Visual Basic NET non supporta più le subroutine Gosub/Return (ed era ora!), e Return viene ora usato per restituire i valori da Function o Property.

Overloading
Una novità per i programmatori Visual Basic introdotta da Visual Basic NET è l'overloading. Si tratta di un meccanismo tramite il quale è possibile avere all'interno di una stessa classe (o form, o modulo etc.) due o più procedure con lo stesso nome, ma con differente numero e/o tipo di parametri.

Questa è una cosa che puo risultare utile in molte situazioni. Se ad esempio vogliamo recuperare un record in un database utilizzando due diversi tipi di chiavi, invece di creare due procedure con due nomi diversi, si possono creare due procedure con lo stesso nome che accettano tipi di parametri diversi.

Per creare due o più procedure overloaded si deve usare la parola chiave Overloads nella dichiarazione della procedura:

  Public Overloads Function FindRecord(ByVal LastName As String)
...
End Function
Public Overloads Function FindRecord(ByVal ID As Integer)
...
End Function

Anche se è possibile inserire argomenti Optional nella dichiarazione di una procedura overloaded, non si possono avere due dichiarazioni che differiscono solo per un parametro optional. Quindi, la seguente coppia di dichiarazioni è illegale:

  Public Overloads Function FindRecord(ByVal CodFisc As String, Optional ByVal c As Integer = 0)
...
End Function
Public Overloads Function FindRecord(ByVal ID As String)
...
End Function

(Il tipo del primo parametro è lo stesso - String - e non ha influenza il diverso nome di variabile - quindi le due funzioni differiscono solo per il parametro optional)

Metodi costruttori
VB 6 supporta i costruttori tramile la parola chiave New, ma questo supporto è lontano dall'essere ottimale. In Visual Basic NET per creare l'istanza di un oggetto, si usa ancora la parola chiave New, ma la sua sintassi è cambiata. In VB 6 si può indifferentemente (vedi nota 2) usare una delle seguenti forme per creare l'istanza di una classe:

  Dim C As New Class1 'costruttore invocato qui

Dim c2 As Class1
Set c2 = New Class1 'costruttore invocato qui

Prima novità, la parola chiave Set non viene più utilizzata per creare istanze di oggetti. Per creare in maniera corretta l'istanza di una classe si possono usare due differenti forme:

  Dim c As CPerson = New CPerson() 'costruttore invocato qui
Dim c2 As CPerson c2 = New CPerson() 'costruttore invocato qui

Queste due sintassi sono assolutamente equivalenti, ma la scomparsa di Set potrebbe causare qualche errore all'inizio.

La seconda novità è che è possibile creare costruttori personalizzati all'interno di una classe, e possiamo parametrizzarli come vogliamo:

  Public Sub New(ByVal dSSN As Decimal)
    mSSN = dSSN
  End Sub

In tal caso, quando si crea un nuovo oggetto di tipo CPerson dovremmo fornire un parametro

  Dim c3 As CPerson = New CPerson(123456789)

In realtà è possibile fare l'overloading di questo costruttore, ed avere una versione senza parametri ed una o piu con parametri.

  Public Sub New()
End Sub
Public Sub New(ByVal dSSN As Decimal) mSSN = dSSN End Sub

Notare però che (almeno con Visual Basic NET Beta 2) non si deve usare la parola chiave Overloads.

Metodi di accesso
I metodi di accesso che eravamo abituati ad usare sono rimasti gli stessi di VB 6, ma ne sono stati aggiunti altri, principalmente per il supporto dell'ereditarietà. Quella che segue è la lista completa.

Metodo

Descrizione

Public

Come Visual Basic 6

Private

Le procedure dichiarate Private sono accessibili solo dall'interno dell'oggetto, inclusa ogni entità annidata.

Friend

Le procedure dichiarate Friend sono accessibili solo dall'interno del progetto che contiene la dichiarazione.

Protected

Le procedure dichiarate Protected sono accessibili solo dall'interno della classe che contiene la dichiarazione o da una classe da questa derivata. L'accesso Protected è specificabile solo per membri di classi.

Protected Friend

Le procedure dichiarate Protected Friend combinano le caratteristiche di Protected e Friend.

Shadows

Indica che questa classe oscura (shadows) un elemento con lo stesso nome risiedente in una classe base. Si può oscurare ogni tipo di elemento con ogni altro tipo. Un elemento oscurato non è disponibile in una classe derivata che lo oscuri.

MustInherit

Indica che questo metodo deve essere implementato dalla classe derivata. Non si possono creare istanze di classi di tipo MustInherit.

NotInheritable

Indica che la classe non può essere ereditata.

Parlerò diffusamente dei nuovi metodi di accesso quando tratterò l'ereditarietà.

Argomenti Optional e ParamArray
Visual Basic NET supporta gli argomenti opzionali, ma è obbligatorio assegnare un valore di default.

Per quanto riguarda l'uso di ParamArray, si deve specificare il tipo:

  Private Sub Test(ByVal ParamArray x() As Integer)
End Sub

Shared
Shared è un'altra novità per i programmatori Visual Basic. Si possono dichiarare condivisibili (Shared) metodi e variabili.
Un metodo dichiarato shared è utilizzabile anche senza creare un'istanza della classe che lo ospita.

  Public Shared Function GetNumber() As Integer
End Function

Quindi, una procedura shared si comporta come se fosse una normale procedura inserita in un modulo, ma con due grandi differenze:

  1. I metodi shared possono accedere solo a dati shared, cioè variabili pubbliche o private dichiarate shared.
    Quindi, il seguente codice produrrà un errore:
  Public Class Test
Private x As Integer
Public Shared Function GetName() As Integer Return x End Function
End Class

Per poter funzionare, anche la variabile x deve essere dichiarate shared:

    Private Shared x As Integer
  1. Mentre non è possibile creare istanze di un modulo, è invece possibile creare due o più istanze di una classe, che quindi si troveranno a condividere le variabili dichiarate Shared.

E su quest'ultimo punto dobbiamo fare chiarezza. Una variabile shared, pubblica o privata che sia, è condivisa da tutte le istanze dell'oggetto. Questo significa che, se tale variabile viene modificata in una istanza, l'effetto si ripercuoterà su tutte le altre istanze! Consideriamo come esempio la seguente classe concepita per gestire un insieme di dischi.

  Public Class CDisco

Private Shared mGroupName As String Private mTitle As String
Public Shared Property GroupName() As String
Get Return mGroupName End Get
Set(ByVal Value As String) mGroupName = Value End Set
End Property
Public Property Title() As String
Get Return mTitle End Get
Set(ByVal Value As String) mTitle = Value End Set
End Property
Public Sub New(ByVal sTitle As String) mTitle = sTitle End Sub
Public Sub New()
End Sub
Public Shared Sub MySharedMethod() Console.WriteLine("---------------------- Gestione Dischi -------------------------") End Sub
End Class

Come si può vedere, a parte i due costruttori, la classe ha solo una proprietà standard chiamata Title per la memorizzazione dei titoli, una proprietà shared chiamata GroupName per il gruppo o cantante ed un metodo shared SharedScriviRiga (qui, spero mi sia perdonata la mancanza di fantasia). Usiamo la classe dall'interno di una procedura di evento di un pulsante all'interno di un form, utilizzando il codice seguente:

 Private Sub Button1_Click(ByVal sender As System.Object, _
                           ByVal e As System.EventArgs) Handles Button1.Click
    
'Shared method called using just the class name CDisco.SharedScriviRiga()
Dim C1 As New CDisco("Insidia") Dim C2 As New CDisco("Pirata")
C1.GroupName = "Litfiba"
Console.WriteLine("Titolo: " & C1.Title & " Gruppo: " & C1.GroupName) Console.WriteLine("Titolo: " & C2.Title & " Gruppo: " & C2.GroupName) End Sub

In questo codice, SharedScriviRiga è chiamato usando il solo nome della classe, senza avere creato alcuna istanza. Inoltre, la proprietà GroupName è stata impostata solo una volta, per l'oggetto C1, ma il risultato, mostrato nella figura seguente, mostra chiaramente che la modifica ha avuto effetto anche sull'oggetto C2.

La spiegazione di questo è abbastanza semplice: le variabili shared vengono allocate solo una volta, e non ogni volta che un nuovo oggetto viene creato. Ogni istanza di una classe contenente una variabile shared fa riferimento all'unica istanza della variabile creata, per cui si ha l'effetto visto sopra.

Static
I dati Static seguono le stesse regole valide per VB6, con una eccezione. In VB6, in una procedure dichiarata Static, tutte le variabili sono automaticamente Static; in Visual Basic NET devono essere dichiarate esplicitamente tali.

Costanti
In Visual Basic NET è possibile definire delle costanti pubbliche all'interno di una classe.

  Public Class TestConst
    Public Const StrConst As String = "Constant String"
    Public Const IntConst As Integer = 10
  End Class

Queste costanti possono essere usate senza creare un'istanza della classe, ma si deve specificare il nome della classe:

  Private Sub Button1_Click(ByVal sender As System.Object, _
                            ByVal e As System.EventArgs) Handles Button1.Click
    MsgBox(TestConst.StrConst)
  End Sub

Se si crea una classe che contiene solo costanti, non ha molto senso poter creare una o più istanze di essa, in quanto tutto quello che può offrire è un elenco di costanti comunque utilizzabile attraverso il nome della classe. Si può quindi creare un costruttore privato, in modo che non sia possibile creare un'istanza della classe stessa.

  Public Class TestConst
Private Sub New()   End Sub
Public Const StrConst As String = "Constant String" Public Const IntConst As Integer = 10 End Class

Inizializzazione e durata
In VB 6 i moduli di classe usano gli eventi Initialize e Terminate per eseguire codice quando le classi vengono create o distrutte. L'evento Initialize viene eseguito la prima volta che un oggetto viene usato dopo un'istruzione New, mentre l'evento Terminate viene eseguito quando l'ultimo riferimento all'oggetto viene rilasciato. Ed all'occorrenza si possono richiamare queste procedure di evento direttamente ogni volta che si vuole.

Visual Basic NET usa invece il paradigma costruttore/distruttore e non supporta più gli eventi Initialize e Terminate. Una classe deve usare il metodo New per dichiarare il suo costruttore, il quale puo essere usato solo quando si crea la classe stessa.

Per quanto riguarda i distruttori le differenze sono anche maggiori, perché il metodo distruttore non è necessariamente chiamato quando l'ultimo riferimento all'oggetto viene rilasciato, e questo vuol dire che è impossibile predire quando l'oggetto sarà distrutto. Ci piaccia o no, questa è una cosa con la quale dobbiamo imparare a convivere. Se abbiamo classi con del codice nell'evento Terminate e desideriamo portarle in Visual Basic NET dobbiamo fare le opportune modifiche. Esistono ovviamente metodologie per affrontare questo problema, ma ne riparlerò quando vedremo il funzionamento del Garbage Collector.

Per chiarimenti o commenti a quanto esposto in questo articolo, potete scrivere all'autore

(segue)

note:

  1. Per la precisione, variabili, strutture ed altri tipi sono stack based mentre classi, forms etc sono heap based. - back
  2. Indifferentemente qui significa che entrambe le forme di creazione portavano all'allocazione di memoria per l'oggetto. In realtà, nella prima forma l'oggetto viene inizializzato la prima volta che viene referenziato nel codice. Il problema di questa maniera di creare gli oggetti sta nel fatto che Visual Basic esegue un controllo sullo stato di inizializzazione dell'oggetto ogni volta che se ne usa qualche proprietà o metodo. - back

L'autore
Stefano Castelli ha iniziato ad occuparsi di programmazione 12 anni fa, dopo avere sostenuto l'esame di "Calcolo Numerico e Programmazione" all'università. In quel tempo
di beata incoscienza si dilettava nella creazione di applicazioni tecnico-scientifiche con il mitico Fortran-4. Una volta resosi conto dell'esistenza di un mondo esterno, si accorse che esistevano anche altri linguaggi di programmazione! Fu quindi iniziato alla programmazione in ambiente Windows (3.1) con VB 3. E da lì...
In seguito, pur avendo intrapreso la 'carriera' di libero professionista, docente etc., riuscì a trovare il tempo di scrivere una decina di articoli per quella bella rivista che fu "Professional Developer Journal", conosciuta anche come "PDJ". Dopo la sua sfortunata chiusura, ha smesso di scrivere per un paio di annetti, essendosi anche nel frattempo trasferito negli USA, dove lavora come Software Engineer responsabile del controllo qualità in una ditta che produce apparecchiature biomediche.