3. Classi, strutture, interfacce
3.1. L'oggetto nell'esempio
3.1.1. Nozioni generali
Affrontiamo ora, attraverso un esempio, la programmazione orientata agli oggetti. Un oggetto è un'entità che contiene dati che ne definiscono lo stato (chiamati proprietà) e funzioni (chiamate metodi). Un oggetto viene creato secondo un modello chiamato classe:
Public Class c1
' attributi
Private p1 As type1
Private p2 As type2
....
' metodo
Public Sub m1(....)
...
End Sub
' metodo
Public Function m2(...)
....
End Function
End Class
Partendo dalla classe precedente C1, è possibile creare numerosi oggetti O1, O2,… Tutti avranno le proprietà p1, p2,… e i metodi m3, m4,… Ma avranno valori diversi per le loro proprietà pi, avendo così ciascuno uno stato proprio. Per analogia, la dichiarazione
crea due oggetti (il termine è improprio in questo contesto) di tipo (classe) Integer. La loro unica proprietà è il loro valore. Se O1 è un oggetto di tipo C1, O1.p1 indica la proprietà p1 di O1 e O1.m1 indica il metodo m1 di O1. Consideriamo un primo modello di oggetto: la classe personne.
3.1.2. Definizione della classe persona
La definizione della classe personne sarà la seguente:
Public Class personne
' attributi
Private prenom As String
Private nom As String
Private age As Integer
' metodo
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metodo
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qui abbiamo la definizione di una classe, quindi di un tipo di dati. Quando creeremo variabili di questo tipo, le chiameremo oggetti o istanze di classi. Una classe è quindi uno stampo a partire dal quale vengono costruiti gli oggetti. I membri o campi di una classe possono essere dati (attributi), metodi (funzioni) o proprietà. Le proprietà sono metodi particolari che servono a conoscere o impostare il valore degli attributi dell’oggetto. Questi campi possono essere accompagnati da una delle tre parole chiave seguenti:
Un campo privato (private) è accessibile solo dai metodi interni della classe | |
Un campo pubblico (public) è accessibile da qualsiasi funzione, definita o meno all’interno della classe | |
Un campo protetto (protected) è accessibile solo tramite i metodi interni della classe o di un oggetto derivato (si veda più avanti il concetto di ereditarietà). |
In generale, i dati di una classe sono dichiarati privati, mentre i suoi metodi e le sue proprietà sono dichiarati pubblici. Ciò significa che l'utente di un oggetto (il programmatore):
- non avrà accesso diretto ai dati privati dell’oggetto
- potrà ricorrere ai metodi pubblici dell’oggetto e in particolare a quelli che consentiranno l’accesso ai suoi dati privati.
La sintassi per la dichiarazione di una classe è la seguente:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
L'ordine di dichiarazione degli attributi private, protected e public è arbitrario.
3.1.3. Il metodo initialize
Torniamo alla nostra classe [personne] dichiarata come:
Public Class personne
' attributi
Private prenom As String
Private nom As String
Private age As Integer
' metodo
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metodo
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qual è la funzione del metodo initialise? Poiché nom, prenom e age sono dati privati della classe personne, le istruzioni:
sono illegali. Dobbiamo inizializzare un oggetto di tipo personne tramite un metodo pubblico. Questo è il ruolo del metodo initialise. Scriveremo:
La sintassi p1.initialise è corretta poiché initialise è di accesso pubblico.
3.1.4. L'operatore new
La sequenza di istruzioni
non è corretta. L'istruzione
dichiara p1 come riferimento a un oggetto di tipo personne. Questo oggetto non esiste ancora e quindi p1 non è inizializzato. È come se si scrivesse:
dove si indica esplicitamente con la parola chiave nothing che la variabile p1 non fa ancora riferimento ad alcun oggetto. Quando poi si scrive
si richiama il metodo initialise dell’oggetto a cui fa riferimento p1. Tuttavia, questo oggetto non esiste ancora e il compilatore segnalerà l’errore. Affinché p1 faccia riferimento a un oggetto, occorre scrivere:
Ciò ha l'effetto di creare un oggetto di tipo personne non ancora inizializzato: gli attributi nom e prenom, che sono riferimenti a oggetti di tipo String, avranno il valore nothing, mentre age assumerà il valore 0. Si verifica quindi un’inizializzazione predefinita. Ora che p1 fa riferimento a un oggetto, l’istruzione di inizializzazione di tale oggetto
è valida.
3.1.5. La parola chiave Me
Diamo un'occhiata al codice del metodo initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
L'istruzione Me.prenom=P significa che l'attributo prenom dell'oggetto corrente (Me) riceve il valore P. La parola chiave Me indica l'oggetto corrente: quello in cui si trova il metodo eseguito. Come lo si riconosce? Vediamo come avviene l'inizializzazione dell'oggetto a cui fa riferimento p1 nel programma chiamante:
Viene chiamato il metodo initialise dell'oggetto p1. Quando in questo metodo si fa riferimento all'oggetto Me, in realtà si fa riferimento all'oggetto p1. Il metodo initialise avrebbe potuto essere scritto anche come segue:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Quando un metodo di un oggetto fa riferimento a un attributo A di tale oggetto, la sintassi Me.A è implicita. È necessario utilizzarla esplicitamente in caso di conflitto tra identificatori. È il caso dell'istruzione:
Me.age=age;
dove age indica sia un attributo dell’oggetto corrente sia il parametro age ricevuto dal metodo. È quindi necessario risolvere l’ambiguità indicando l’attributo age con Me.age.
3.1.6. Un programma di prova
Ecco un breve programma di test:
Public Class personne
' attributi
Private prenom As String
Private nom As String
Private age As Integer
' metodo
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metodo
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
e i risultati ottenuti:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Utilizzo di un file di classi compilate (assembly)
Si noti che nell'esempio precedente nel nostro programma di test sono presenti due classi: le classi personne e test1. Esiste un altro modo di procedere:
-
si compila la classe persona in un file specifico chiamato assembly. Questo file ha estensione .dll
-
si compila la classe test1 facendo riferimento all’assembly che contiene la classe persona.
I due file sorgente diventano i seguenti:
test.vb | |
personne2.vb | |
La classe personne viene compilata con la seguente istruzione:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
La compilazione ha generato un file personne2.dll. È l'opzione di compilazione /t:library che indica di generare un file "assembly". Ora compiliamo il file test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
L'opzione di compilazione /r:personne2.dll indica al compilatore che troverà alcune classi nel file personne2.dll. Quando nel file sorgente test.vb troverà un riferimento alla classe personne, classe non dichiarata nel sorgente test.vb, cercherà la classe personne nei file .dll a cui fa riferimento l'opzione /r. Qui troverà la classe personne nell'assembly personne2.dll. In questo assembly si sarebbero potute inserire altre classi. Per utilizzare durante la compilazione più file di classi compilate, si scriverà:
L'esecuzione del programma test1.exe fornisce i seguenti risultati:
3.1.8. Un altro metodo si inizializza
Consideriamo ancora la classe personne e aggiungiamo ad essa il seguente metodo:
' metodo
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Ora abbiamo due metodi con il nome initialise: ciò è consentito purché accettino parametri diversi. In questo caso è così. Il parametro è ora un riferimento P a una persona. Gli attributi della persona P vengono quindi assegnati all’oggetto corrente (Me). Si noti che il metodo initialise ha accesso diretto agli attributi dell’oggetto P, sebbene questi siano di tipo private. Questo vale sempre: un oggetto O1 di una classe C ha sempre accesso agli attributi degli oggetti della stessa classe C. Ecco un test della nuova classe personne, che è stata compilata in personne.dll come spiegato in precedenza:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e i relativi risultati:
3.1.9. Costruttori della classe persona
Un costruttore è una procedura denominata New che viene chiamata al momento della creazione dell'oggetto. Generalmente viene utilizzato per inizializzarlo. Se una classe ha un costruttore che accetta n argomenti argi, la dichiarazione e l'inizializzazione di un oggetto di tale classe potranno avvenire nella forma:
dim objet as classe =new classe(arg1,arg2, ... argn)
oppure
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Quando una classe ha uno o più costruttori, è obbligatorio utilizzare uno di questi costruttori per creare un oggetto di quella classe. Se una classe C non ha alcun costruttore, ne ha uno predefinito, ovvero il costruttore senza parametri: public New(). Gli attributi dell'oggetto vengono quindi inizializzati con i valori predefiniti. È ciò che è accaduto nei programmi precedenti, in cui avevamo scritto:
Creiamo due costruttori per la nostra classe personne:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' la classe persona
Public Class personne
' attributi
Private prenom As String
Private nom As String
Private age As Integer
' costruttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' metodi di inizializzazione dell'oggetto
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' metodo
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
I nostri due costruttori si limitano a richiamare i metodi initialise corrispondenti. Ricordiamo che quando in un costruttore si trova, ad esempio, la notazione initialise(P), il compilatore la traduce in Me.initialise(P). Nel costruttore, il metodo initialise viene quindi chiamato per operare sull’oggetto a cui fa riferimento Me, ovvero l’oggetto corrente, quello in fase di costruzione. Ecco un breve programma di prova:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
e i risultati ottenuti:
3.1.10. I riferimenti agli oggetti
Utilizziamo sempre la stessa classe personne. Il programma di test diventa il seguente:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 fa riferimento allo stesso oggetto di p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 fa riferimento a un oggetto che sarà una copia dell'oggetto a cui fa riferimento p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' si modifica lo stato dell'oggetto a cui fa riferimento p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' poiché p2=p1, l'oggetto a cui fa riferimento p2 deve aver cambiato stato
Console.Out.Write("p2=")
p2.identifie()
' poiché p3 non fa riferimento allo stesso oggetto di p1, l'oggetto a cui fa riferimento p3 non deve essere cambiato
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
I risultati ottenuti sono i seguenti:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Quando si dichiara la variabile p1 tramite
p1 fa riferimento all’oggetto personne("Jean","Dupont",30) ma non è l’oggetto stesso. In C, si direbbe che si tratta di un puntatore, c.a.d, all’indirizzo dell’oggetto creato. Se poi si scrive:
non è l’oggetto personne("Jean","Dupont",30) a essere modificato, bensì è il riferimento p1 a cambiare valore. L’oggetto personne("Jean","Dupont",30) andrà “perso” se non è referenziato da nessun’altra variabile.
Quando si scrive:
si inizializza il puntatore p2: esso “punta” allo stesso oggetto (indica lo stesso oggetto) del puntatore p1. Pertanto, se si modifica l’oggetto “puntato” (o referenziato) da p1, si modifica anche quello referenziato da p2.
Quando si scrive:
viene creato un nuovo oggetto, copia dell’oggetto a cui fa riferimento p1. Questo nuovo oggetto sarà referenziato da p3. Se si modifica l’oggetto “puntato” (o a cui si fa riferimento) da p1, non si modifica in alcun modo quello a cui fa riferimento p3. È quanto dimostrano i risultati ottenuti.
3.1.11. Gli oggetti temporanei
In un'espressione è possibile richiamare esplicitamente il costruttore di un oggetto: quest'ultimo viene creato, ma non vi abbiamo accesso (ad esempio per modificarlo). Questo oggetto temporaneo viene creato per le esigenze di valutazione dell'espressione e poi eliminato. Lo spazio di memoria che occupava verrà automaticamente recuperato in seguito da un programma chiamato «garbage collector», il cui ruolo è quello di recuperare lo spazio di memoria occupato da oggetti che non sono più referenziati dai dati del programma. Consideriamo il seguente nuovo programma di test:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
e modifichiamo i costruttori della classe personne in modo che visualizzino un messaggio:
' costruttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Otteniamo i seguenti risultati:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
che mostrano la creazione progressiva dei due oggetti temporanei.
3.1.12. Metodi di lettura e scrittura degli attributi privati
Aggiungiamo alla classe personne i metodi necessari per leggere o modificare lo stato degli attributi degli oggetti:
Imports System
Public Class personne
' attributi
Private prenom As [String]
Private nom As [String]
Private age As Integer
' costruttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identificatori
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessori
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modificatori
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Testiamo la nuova classe con il seguente programma:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' pagina di prova
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
e otteniamo i seguenti risultati:
3.1.13. Le proprietà
Esiste un altro modo per accedere agli attributi di una classe: creare delle proprietà. Queste ci consentono di manipolare gli attributi privati come se fossero pubblici. Consideriamo la seguente classe personne, in cui gli accessori e i modificatori precedenti sono stati sostituiti da proprietà in lettura e scrittura:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' classe persona
Public Class personne
' attributi
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' produttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' proprietà
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' età valida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Una proprietà (Property) consente di leggere (get) o impostare (set) il valore di un attributo. Nel nostro esempio, abbiamo anteposto il carattere _ ai nomi degli attributi in modo che le proprietà abbiano lo stesso nome degli attributi primitivi. Infatti, una proprietà non può avere lo stesso nome dell’attributo che gestisce, poiché in tal caso si verificherebbe un conflitto di nomi all’interno della classe. Abbiamo quindi denominato i nostri attributi _prenom, _nom, _age e modificato i costruttori e i metodi di conseguenza. Abbiamo quindi creato tre proprietà: nom, prenom e age. Una proprietà viene dichiarata come segue:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
dove Type deve essere il tipo dell'attributo gestito dalla proprietà. Essa può avere due metodi denominati get e set. Il metodo get ha solitamente il compito di restituire il valore dell'attributo che gestisce (potrebbe restituire qualcos'altro, nulla lo impedisce). Il metodo set riceve un parametro denominato value che normalmente assegna all’attributo che gestisce. Può approfittarne per verificare la validità del valore ricevuto ed eventualmente generare un’eccezione se il valore risulta non valido. È ciò che viene fatto qui per l’età.
Come vengono chiamati questi metodi get e set? Consideriamo il seguente programma di test:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
Nell’istruzione
si cerca di ottenere i valori delle proprietà prenom, nom e age della persona P. Viene quindi chiamato il metodo get di queste proprietà, che restituisce il valore dell’attributo da esse gestito.
Nell'istruzione
si desidera impostare il valore della proprietà age. Viene quindi chiamato il metodo set di questa proprietà, che riceverà il valore 56 nel suo parametro value.
Una proprietà P di una classe C che definisca solo il metodo get è detta di sola lettura. Se c è un oggetto della classe C, l’operazione c.P=valore verrà quindi rifiutata dal compilatore.
L'esecuzione del programma di test precedente fornisce i seguenti risultati:
Le proprietà ci consentono quindi di manipolare gli attributi privati come se fossero pubblici.
3.1.14. Metodi e attributi di classe
Supponiamo di voler contare il numero di oggetti [personne] creati in un'applicazione. Si potrebbe gestire manualmente un contatore, ma si rischia di dimenticare gli oggetti temporanei creati qua e là. Sembrerebbe più sicuro includere nei costruttori della classe [personne] un’istruzione che incrementi un contatore. Il problema è passare un riferimento a questo contatore affinché il costruttore possa incrementarlo: è necessario passare loro un nuovo parametro. Si può anche includere il contatore nella definizione della classe. Poiché si tratta di un attributo della classe stessa e non di un oggetto specifico di tale classe, lo si dichiara in modo diverso con la parola chiave Shared:
Per fare riferimento ad esso, si scrive personne._nbPersonnes per indicare che si tratta di un attributo della classe personne stessa. In questo caso, abbiamo creato un attributo privato a cui non si avrà accesso diretto al di fuori della classe. Creiamo quindi una proprietà pubblica per consentire l’accesso all’attributo della classe nbPersonnes. Per impostare il valore di nbPersonnes, il metodo get di questa proprietà non necessita di un oggetto personne specifico: infatti _nbPersonnes non è l’attributo di un oggetto specifico, ma è l’attributo di un’intera classe. È quindi necessaria una proprietà dichiarata anch’essa come Shared:
che dall'esterno verrà richiamata con la sintassi personne.nbPersonnes. La proprietà è dichiarata di sola lettura (ReadOnly) poiché non prevede un metodo set. Ecco un esempio. La classe personne diventa la seguente:
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' classe
Public Class personne
' attributi di classe
Private Shared _nbPersonnes As Long = 0
' attributi di istanza
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' costruttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' un'altra persona
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' un'altra persona
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' proprietà di classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' proprietà di istanza
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' età valida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Con il seguente programma:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pg di prova
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
si ottengono i seguenti risultati:
3.1.15. Passaggio di un oggetto a una funzione
Abbiamo già detto che, per impostazione predefinita, VB.NET passa i parametri effettivi di una funzione per valore: i valori dei parametri effettivi vengono copiati nei parametri formali. Nel caso di un oggetto, non bisogna lasciarsi ingannare dall'uso improprio del termine che viene sistematicamente fatto quando si parla di oggetto invece che di riferimento a un oggetto. Un oggetto viene manipolato solo tramite un riferimento (un puntatore) ad esso. Ciò che viene quindi trasmesso a una funzione non è l’oggetto stesso, ma un riferimento a tale oggetto. È quindi il valore del riferimento, e non il valore dell’oggetto stesso, che viene duplicato nel parametro formale: non viene creato alcun nuovo oggetto. Se un riferimento all’oggetto R1 viene passato a una funzione, verrà copiato nel parametro formale corrispondente R2. Pertanto, i riferimenti R2 e R1 indicano lo stesso oggetto. Se la funzione modifica l’oggetto a cui punta R2, modifica ovviamente anche quello a cui fa riferimento R1, poiché si tratta dello stesso oggetto.
![]() |
È quanto illustra il seguente esempio:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
' una persona p1
Dim p1 As New personne("Jean", "Dupont", 30)
' visualizzazione p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modifica p1
modifie(p1)
' visualizzazione p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' visualizzazione persona P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modifica P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' visualizzazione P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
I risultati ottenuti sono i seguenti:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Si nota che è stato creato un solo oggetto: quello della persona p1 della procedura Main e che l'oggetto è stato effettivamente modificato dalla funzione modifie.
3.1.16. Un array di persone
Un oggetto è un dato come un altro e, in quanto tale, più oggetti possono essere raggruppati in un array:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
' tabella delle persone
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' visualizzazione
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
L'istruzione Dim amici(2) As persona crea un array di 3 elementi di tipo personne. Questi 3 elementi sono qui inizializzati con i valori nothing e c.a.d, poiché non fanno riferimento ad alcun oggetto. Ancora una volta, per convenzione, si parla di array di oggetti, mentre si tratta semplicemente di un array di riferimenti a oggetti. La creazione dell’array di oggetti, che è esso stesso un oggetto, non crea alcun oggetto del tipo dei suoi elementi: ciò va fatto in seguito. Si ottengono i seguenti risultati:
3.2. L'ereditarietà attraverso l'esempio
3.2.1. Nozioni generali
Qui affrontiamo il concetto di ereditarietà. Lo scopo dell’ereditarietà è quello di “personalizzare” una classe esistente affinché soddisfi le nostre esigenze. Supponiamo di voler creare una classe enseignant: un insegnante è una persona particolare. Ha degli attributi che un'altra persona non avrà: la materia che insegna, ad esempio. Ma possiede anche gli attributi di ogni persona: nome, cognome ed età. Un insegnante fa quindi pienamente parte della classe personne, ma presenta attributi aggiuntivi. Anziché creare una classe enseignant partendo da zero, preferiremmo riutilizzare gli elementi della classe personne, adattandoli alle caratteristiche specifiche degli insegnanti. È il concetto di ereditarietà che ci permette di farlo. Per indicare che la classe enseignant eredita le proprietà della classe personne, scriveremo:
Public Class enseignant
Inherits personne
Si noti la particolare sintassi su due righe. La classe personne è detta classe padre (o madre) e la classe enseignant classe derivata (o figlia). Un oggetto enseignant possiede tutte le caratteristiche di un oggetto personne: ha gli stessi attributi e gli stessi metodi. Questi attributi e metodi della classe padre non vengono ripetuti nella definizione della classe figlia: ci si limita a indicare gli attributi e i metodi aggiunti dalla classe figlia. Supponiamo che la classe personne sia definita come segue:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' classe persona
Public Class personne
' attributi di classe
Private Shared _nbPersonnes As Long = 0
' attributi di istanza
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' costruttori
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' un'altra persona
_nbPersonnes += 1
' costruzione
Me._prenom = P
Me._nom = N
Me._age = age
' monitoraggio
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' un'altra persona
_nbPersonnes += 1
' costruzione
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' monitoraggio
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' proprietà di classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' proprietà dell'istanza
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' età valida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
Il metodo identifie è stato sostituito dalla proprietà identité, di sola lettura, che identifica la persona. Creiamo una classe enseignant che eredita dalla classe personne:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Public Class enseignant
Inherits personne
' attributi
Private _section As Integer
' costruttore
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' monitoraggio
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' proprietà sezione
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
La classe enseignant aggiunge ai metodi e agli attributi della classe personne:
- un attributo section, che corrisponde al numero di sezione a cui appartiene il docente all'interno del corpo docente (in linea di massima, una sezione per ogni disciplina)
- un nuovo costruttore che consente di inizializzare tutti gli attributi di un docente
La dichiarazione
Public Class enseignant
Inherits personne
indica che la classe enseignant deriva dalla classe personne.
3.2.2. Creazione di un oggetto docente
Il costruttore della classe enseignant è il seguente:
' costruttore
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' monitoraggio
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
La dichiarazione
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
dichiara che il costruttore riceve quattro parametri: P, N, age, section. Deve passarne tre, (P,N,age), alla sua classe base, in questo caso la classe personne. Sappiamo che questa classe ha un costruttore persona(string, string, int) che consentirà di creare una persona con i parametri passati (P,N,age). La classe [enseignant] passa i parametri (P, N, età) alla sua classe base nel modo seguente:
MyBase.New(P, N, age)
Una volta completata la costruzione della classe base, la costruzione dell’oggetto enseignant prosegue con l’esecuzione del corpo del costruttore:
Me._section = section
In sintesi, il costruttore di una classe derivata:
- passa alla sua classe base i parametri necessari per la sua creazione
- utilizza gli altri parametri per inizializzare i propri attributi
Si sarebbe potuto preferire scrivere:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' tracciabilità
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Non è possibile. La classe personne ha dichiarato privati (private) i suoi tre campi _prenom, _nom e _age. Solo gli oggetti della stessa classe hanno accesso diretto a questi campi. Tutti gli altri oggetti, compresi gli oggetti derivati come in questo caso, devono passare attraverso metodi pubblici per accedervi. La situazione sarebbe stata diversa se la classe personne avesse dichiarato protetti (protected) i tre campi: in tal caso avrebbe consentito alle classi derivate di avere accesso diretto ai tre campi. Nel nostro esempio, utilizzare il costruttore della classe padre era quindi la soluzione corretta ed è il metodo usuale: durante la creazione di un oggetto figlio, si chiama prima il costruttore dell’oggetto padre, poi si completano le inizializzazioni specifiche dell’oggetto figlio (section nel nostro esempio).
Compiliamo le classi personne e enseignant in assembly:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Si noti che per compilare la classe figlia enseignant è stato necessario fare riferimento al file personne.dll, che contiene la classe personne. Proviamo a scrivere un primo programma di prova:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Questo programma si limita a creare un oggetto enseignant (new) e a identificarlo. La classe enseignant non dispone del metodo identité, ma la sua classe padre ne possiede uno che, per di più, è pubblico: per eredità, esso diventa un metodo pubblico della classe enseignant. I risultati ottenuti sono i seguenti:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Si nota che:
- un oggetto personne è stato creato prima dell'oggetto enseignant
- l'identità ottenuta è quella dell'oggetto personne
3.2.3. Sovraccarico di un metodo o di una proprietà
Nell’esempio precedente abbiamo ottenuto l’identità della parte personne relativa al docente, ma mancano alcune informazioni specifiche della classe enseignant (la sezione). Si è quindi necessario scrivere una proprietà che consenta di identificare il docente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Public Class enseignant
Inherits personne
' attributi
Private _section As Integer
' costruttore
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' monitoraggio
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' proprietà sezione
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' sovraccarico della proprietà identità
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Il metodo identite della classe enseignant si basa sul metodo identite della sua classe madre (MyBase.identite) per visualizzarne la parte «personne», quindi la completa con il campo _section, specifico della classe enseignant. Si noti la dichiarazione della proprietà identite:
Public Shadows ReadOnly Property identite() As String
che indica che la proprietà «identite» «nasconde» il metodo omonimo che potrebbe esistere nella classe padre. Si consideri un oggetto enseignant E. Questo oggetto contiene al suo interno un oggetto personne:
![]() |
La proprietà identity è definita sia nella classe enseignant che nella sua classe padre personne. Nella classe figlia enseignant, la proprietà identite deve essere preceduta dalla parola chiave shadows per indicare che si sta ridefinendo una nuova proprietà identite per la classe enseignant.
Public Shadows ReadOnly Property identite() As String
La classe enseignant dispone ora di due proprietà identite:
- quella ereditata dalla classe padre personne
- una propria
Se E è un oggetto enseignant, E.identite indica il metodo identite della classe enseignant. Si dice che la proprietà identite della classe madre sia «ridefinita» dalla proprietà identite della classe figlia. In generale, se O è un oggetto e M è un metodo, per eseguire il metodo O.M, il sistema cerca un metodo M nel seguente ordine:
- nella classe dell’oggetto O
- nella sua classe padre, se ne ha una
- nella classe madre della sua classe madre, se esiste
- ecc…
L’ereditarietà consente quindi di ridefinire nella classe figlia metodi/proprietà con lo stesso nome presenti nella classe madre. È questo che permette di adattare la classe figlia alle proprie esigenze. Insieme al polimorfismo, di cui parleremo più avanti, il sovraccarico di metodi/proprietà rappresenta il principale vantaggio dell’ereditarietà. Consideriamo lo stesso esempio di prima:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
I risultati ottenuti questa volta sono i seguenti:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Il polimorfismo
Consideriamo una serie di classi: C0 C1 C2 … Dove Ci Cj indica che la classe Cj deriva dalla classe Ci. Ne consegue che la classe Cj possiede tutte le caratteristiche della classe Ci più altre. Siano Oi oggetti di tipo Ci. È lecito scrivere:
Infatti, per ereditarietà, la classe Cj possiede tutte le caratteristiche della classe Ci più altre. Pertanto, un oggetto Oj di tipo Cj contiene al suo interno un oggetto di tipo Ci. L’operazione
fa sì che Oi sia un riferimento all’oggetto di tipo Ci contenuto nell’oggetto Oj.
Il fatto che una variabile Oi della classe Ci possa di fatto fare riferimento non solo a un oggetto della classe Ci, ma a qualsiasi oggetto derivato dalla classe Ci, è chiamato polimorfismo: la capacità di una variabile di fare riferimento a diversi tipi di oggetti. Facciamo un esempio e consideriamo la seguente funzione indipendente da qualsiasi classe:
Sub affiche(ByVal p As personne)
Si potrebbe anche scrivere
che
In quest'ultimo caso, il parametro formale di tipo personne della funzione affiche riceverà un valore di tipo enseignant. Poiché il tipo enseignant deriva dal tipo personne, ciò è consentito.
3.2.5. Ridefinizione e polimorfismo
Completiamo la nostra procedura affiche:
Sub affiche(ByVal p As personne)
' visualizza identità di p
Console.Out.WriteLine(p.identite)
End Sub
Il metodo p.identite restituisce una stringa che identifica l’oggetto personne.. Cosa succede, nel caso del nostro esempio precedente, con un oggetto enseignant:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Esaminiamo il seguente esempio:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
' un insegnante
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' visualizza
Sub affiche(ByVal p As personne)
' visualizza identità di p
Console.Out.WriteLine(p.identite)
End Sub
End Module
I risultati ottenuti sono i seguenti:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
L'esecuzione mostra che l'istruzione p.identite ha eseguito ogni volta la proprietà identite di una personne, la persona contenuta nell'enseignant e, quindi la stessa personne p. Non si è adattata all'oggetto effettivamente passato come parametro a affiche. Avremmo preferito avere l’identità completa dell’oggetto enseignant. A tal fine, la notazione p.identite avrebbe dovuto fare riferimento alla proprietà identite dell’oggetto effettivamente puntato da p, anziché alla proprietà identite della parte "personne" dell’oggetto effettivamente puntato da p. È possibile ottenere questo risultato dichiarando identite come proprietà sovrascrivibile (overridable) nella classe base personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
La parola chiave **overridable rende identite una proprietà sovrascrivibile o virtuale. Questa parola chiave può essere applicata anche ai metodi. Le classi figlie che ridefiniscono una proprietà o un metodo virtuale devono quindi utilizzare la parola chiave overrides anziché **shadows** per qualificare la proprietà/il metodo ridefinito. Pertanto, nella classe enseignant, la proprietà identite è definita come segue:
' sovrascrittura della proprietà identità
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Il programma di test:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
' un insegnante
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' visualizza
Sub affiche(ByVal p As personne)
' mostra identità di p
Console.Out.WriteLine(p.identite)
End Sub
End Module
produce quindi i seguenti risultati:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Questa volta abbiamo ottenuto l'identità completa del docente. Ridefiniamo ora un metodo anziché una proprietà. La classe object è la classe "madre" di tutte le classi VB.NET. Pertanto, quando si scrive:
si scrive implicitamente:
La classe object definisce un metodo virtuale ToString:

Il metodo ToString restituisce il nome della classe a cui appartiene l'oggetto, come mostra l'esempio seguente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test2
Sub Main()
' un insegnante
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' una persona
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
I risultati ottenuti sono i seguenti:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Si noti che, sebbene non abbiamo ridefinito il metodo ToString nelle classi personne e enseignant, si può comunque constatare che il metodo ToString della classe object è comunque in grado di visualizzare il nome effettivo della classe dell'oggetto. Ridefiniamo il metodo ToString nelle classi personne e enseignant:
' ToString
Public Overrides Function ToString() As String
' si assegna la proprietà «identità»
Return identite
End Function
La definizione è la stessa in entrambe le classi. Consideriamo il seguente programma di test:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
' un insegnante
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' manifesto
Sub affiche(ByVal p As personne)
' mostra identità di p
Console.Out.WriteLine(p.identite)
End Sub
End Module
I risultati dell'esecuzione sono i seguenti:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Definire un indicizzatore per una classe
Consideriamo la classe predefinita [ArrayList] nella piattaforma .NET. Questa classe consente di memorizzare oggetti in un elenco. Appartiene allo spazio dei nomi [System.Collections]. Nell’esempio seguente utilizziamo questa classe per memorizzare un elenco di persone (in senso lato):
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' per aggiungere una persona all'elenco
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' un indicizzatore
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' un altro indicizzatore
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' si cerca la persona di nome N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' restituisce (elemento1, elemento2, ..., elementoN)
Dim liste As String = "("
Dim i As Integer
' si percorre l'array dinamico
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ultimo elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Forniamo alcune informazioni su alcune proprietà e metodi della classe [ArrayList]:
attributo che indica il numero di elementi dell’elenco | |
metodo che consente di aggiungere un oggetto all’elenco | |
metodo che restituisce l'elemento i della lista |
Notiamo che per ottenere l'elemento n. i della lista, non abbiamo scritto [liste.Item(i)] ma direttamente [liste(i)], il che a prima vista sembra errato. Ciò è tuttavia possibile perché la classe [ArrayList] definisce una proprietà predefinita [Item] secondo una sintassi analoga alla seguente:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Quando il compilatore incontra la notazione [liste(i)], verifica se la classe [ArrayList] ha definito una proprietà con la seguente firma:
Default Public Property Proc(ByVal var As Integer) As Type
In questo caso, troverà la procedura [Item]. Traduce quindi la sintassi [liste(i)] in [liste.Item(i)]. La proprietà [Item] è denominata proprietà indicizzata predefinita della classe [ArrayList]. L'esecuzione del programma precedente fornisce i seguenti risultati:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Creiamo una classe denominata [listeDePersonnes] che rappresenti un elenco di persone, ovvero un elenco specifico che sembra naturale derivare dalla classe [ArrayList]:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' per aggiungere una persona alla lista
Public Shadows Sub Add(ByVal p As Object)
' p deve essere una persona
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' un indicizzatore
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' restituisce (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' si percorre l'array dinamico
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ultimo elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
La classe presenta i seguenti metodi e proprietà:
restituisce una stringa di caratteri che «rappresenta» il contenuto dell’elenco | |
metodo che consente di aggiungere una persona all'elenco | |
proprietà indicizzata per impostazione predefinita che restituisce la persona i dell'elenco |
Consideriamo le novità:
È stato creato un nuovo metodo [Add].
' per aggiungere una persona alla lista
Public Shadows Sub Add(ByVal p As Object)
' è necessario che p sia una persona
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
Ne esiste già una nella classe padre [ArrayList] con la stessa firma, da cui la parola chiave [Shadows] per indicare che la nuova procedura sostituisce quella della classe padre. Il metodo [Add] della classe figlia verifica che l’oggetto aggiunto sia effettivamente di tipo [personne] o derivato tramite la funzione [TypeOf]. In caso contrario, viene generata un'eccezione con l'istruzione [Throw]. Se l'oggetto aggiunto è effettivamente di tipo [personne], viene utilizzato il metodo [Add] della classe base per effettuare l'aggiunta.
Viene creata una proprietà indicizzata:
' un indicizzatore
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
Nella classe base esiste già una proprietà predefinita denominata [Item] con la stessa firma. Pertanto, è necessario utilizzare la parola chiave [Shadows] per indicare che la nuova proprietà indicizzata [Index] "nasconderà" quella della classe base [Item]. Si noti che ciò vale anche se le due proprietà non hanno lo stesso nome. La proprietà [Index] consente di fare riferimento alla persona n. i dell’elenco. Si basa sulla proprietà [Item] della classe base per accedere all’elemento n. i dell’oggetto sottostante [ArrayList]. Vengono apportate modifiche al tipo per tenere conto del fatto che la proprietà [Item] opera con elementi di tipo [Object], mentre la proprietà [Index] opera con elementi di tipo [personne].
Infine, ridefiniamo (Overrides) il metodo [ToString] della classe [ArrayList]:
' toString
Public Overrides Function ToString() As String
' restituisce (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' si percorre l'array dinamico
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' ultimo elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Questo metodo restituisce una stringa nella forma "(e1,e2,...,en)", dove e1, e2, ..., en sono gli elementi della lista. Si noti la notazione [Me(i)], che indica l'elemento n. i dell'oggetto corrente [Me]. Viene utilizzata la proprietà indicizzata predefinita. Pertanto, [Me(i)] è equivalente a [Me.Index(i)].
Il codice della classe viene inserito nel file [lstpersonnes2.vb] e compilato:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
Viene creato un programma di test:
' opzioni
Option Explicit On
Option Strict On
' spazi dei nomi
Imports System
Imports System.Collections
Module test
Sub Main()
' creazione di un elenco vuoto di persone
Dim liste As listeDePersonnes = New listeDePersonnes
' creazione di persone
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' compilazione dell'elenco
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' visualizzazione dell'elenco
Console.Out.WriteLine(liste.ToString)
' aggiunta di un oggetto diverso da una persona
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Viene compilato:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
quindi eseguito:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Si potrebbe voler scrivere
dove l sarebbe del tipo [listeDePersonnes]. In questo caso, si desidera indicizzare l'elenco l non più in base al numero dell'elemento, ma in base al nome di una persona. A tal fine, si definisce una nuova proprietà indicizzata per impostazione predefinita:
' un altro indicizzatore
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' si cerca la persona di nome N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
La prima riga
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indica che si sta creando una nuova proprietà indicizzata predefinita. Tutte le proprietà predefinite devono avere lo stesso nome, in questo caso [Index]. La nuova proprietà [Index] indicizza la classe listeDePersonnes tramite una stringa di caratteri N. Il risultato di listeDePersonnes(N) è un numero intero. Questo numero intero corrisponderà alla posizione nell’elenco della persona con il nome N oppure a -1 se tale persona non è presente nell’elenco. Si definisce solo la proprietà get, impedendo così la scrittura di listeDePersonnes ("nom")=valeur, che avrebbe richiesto la definizione della proprietà set). Da qui la parola chiave [ReadOnly]. La parola chiave [Shadows] è necessaria per nascondere la proprietà predefinita della classe base (sebbene non abbia la stessa firma).
Nel corpo di get, si scorre l’elenco delle persone alla ricerca del nome N passato come parametro. Se lo si trova nella posizione i, si restituisce i, altrimenti si restituisce -1.
Un nuovo programma di test potrebbe essere il seguente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' pagina di prova
Module test
Sub Main()
' un elenco di persone
Dim l As New listeDePersonnes
' aggiunta di persone
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' visualizzazione
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' modifica elemento 1
l(1) = New personne("sylvie", "cachan", 5)
' visualizzazione elemento 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' visualizzazione elenco l
Console.Out.WriteLine(("l=" + l.ToString))
' ricerca di persone
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
L’esecuzione fornisce i seguenti risultati:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. Le strutture
La struttura VB.NET deriva direttamente dalla struttura del linguaggio C ed è molto simile a una classe. Una struttura è definita come segue:
Structure spersonne
' attributi
...
' proprietà
...
' costruttori
...
' metodi
End Structure
Nonostante la somiglianza nella sintassi, esistono differenze significative tra classe e struttura. Il concetto di ereditarietà, ad esempio, non esiste nelle strutture. Se si scrive una classe che non deve essere derivata, quali sono le differenze tra struttura e classe che ci aiuteranno a scegliere tra le due? Utilizziamo il seguente esempio per scoprirlo:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' struttura spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' classe persona
Class cpersonne
Public nom As String
Public age As Integer
End Class
' un modulo di test
Public Module test
Sub Main()
' una persona p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' una persona p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 è stato modificato
sp2.nom = "nicole"
sp2.age = 30
' verifica di sp1 e sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' una persona cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' una persona c P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 è stato modificato
cp2.nom = "nicole"
cp2.age = 30
' verifica di P1 e P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Se si esegue questo programma, si ottengono i seguenti risultati:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Laddove nelle pagine precedenti di questo capitolo si utilizzava una classe personne, ora utilizziamo una struttura spersonne:
' struttura persona
Structure spersonne
Public nom As String
Public age As Integer
End Structure
La dichiarazione
Dim sp1 As spersonne
crea una struttura (nome, età) e il valore di sp1 è proprio questa struttura.
La dichiarazione
Dim cp1 As New cpersonne
crea un oggetto [cpersonne] (più o meno l'equivalente della nostra struttura) e cp1 è quindi l'indirizzo (il riferimento) di tale oggetto.
Riassumiamo
- nel caso della struttura, il valore di sp1 è la struttura stessa
- nel caso della classe, il valore di p1 è l’indirizzo dell’oggetto creato
![]() |
![]() |
Quando nel programma si scrive
Dim sp2 As spersonne = sp1
viene creata una nuova struttura (nome, età) e inizializzata con il valore di p1, ovvero la struttura stessa.
![]() |
La struttura di sp1 viene quindi duplicata in sp2. Si tratta di una copia per valore.
L'istruzione
Dim cp2 As cpersonne = cp1
agisce in modo diverso. Il valore di cp1 viene copiato in cp2, ma poiché tale valore è in realtà l’indirizzo dell’oggetto, quest’ultimo non viene duplicato. Ha semplicemente due riferimenti a sé:
![]() |
Nel caso della struttura, se si modifica il valore di sp2 non si modifica il valore di sp1, come dimostra il programma. Nel caso dell’oggetto, se si modifica l’oggetto a cui punta cp2, viene modificato anche quello a cui punta cp1, poiché si tratta dello stesso oggetto. È quanto dimostrano anche i risultati del programma.
Da queste spiegazioni si ricava quindi che:
- il valore di una variabile di tipo struttura è la struttura stessa
- il valore di una variabile di tipo oggetto è l’indirizzo dell’oggetto a cui punta
Una volta compresa questa differenza fondamentale, la struttura risulta molto simile alla classe, come dimostra il seguente nuovo esempio:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' struttura persona
Structure personne
' attributi
Private _nom As String
Private _age As Integer
' proprietà
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Costruttore
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' un modulo di test
Module test
Sub Main()
' una persona p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' una persona p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 è stato modificato
p2.nom = "nicole"
p2.age = 30
' verifica di p1 e p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Si ottengono i seguenti risultati di esecuzione:
L'unica differenza degna di nota tra struttura e classe è che, con una classe, gli oggetti p1 e p2 avrebbero avuto lo stesso valore alla fine del programma, ovvero quello di p2.
3.5. Le interfacce
Un'interfaccia è un insieme di prototipi di metodi o proprietà che costituisce un contratto. Una classe che decide di implementare un'interfaccia si impegna a fornire un'implementazione di tutti i metodi definiti nell'interfaccia. È il compilatore a verificare tale implementazione. Ecco, ad esempio, la definizione dell'interfaccia Istats:
Ogni classe che implementa questa interfaccia verrà dichiarata come
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
I metodi [moyenne] e [écartType] dovranno essere definiti nella classe C. Consideriamo il codice seguente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' struttura
Public Structure élève
Public _nom As String
Public _note As Double
' costruttore
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' classe note
Public Class notes
' attributo
Protected _matière As String
Protected _élèves() As élève
' produttore
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' registrazione studenti e materie
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' si concatenano tutti i voti
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'ultimo voto
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' fine
Return valeur
End Function
End Class
La classe notes raggruppa i voti di una classe in una materia:
Public Class notes
' attributo
Protected _matière As String
Protected _élèves() As élève
Gli attributi sono dichiarati come protected per essere accessibili da una classe derivata. Il tipo élève è una struttura che memorizza il nome dello studente e il suo voto nella materia:
Public Structure élève
Public _nom As String
Public _note As Double
' costruttore
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Decidiamo di derivare questa classe notes in una classe notesStats che avrebbe due attributi aggiuntivi, la media e la deviazione standard dei voti:
Public Class notesStats
Inherits notes
Implements Istats
' attributi
Private _moyenne As Double
Private _écartType As Double
La classe notesStats implementa la seguente interfaccia Istats:
Ciò significa che la classe notesStats deve avere due metodi denominati moyenne e écartType con la firma indicata nell'interfaccia Istats. La classe notesStats è la seguente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributi
Private _moyenne As Double
Private _écartType As Double
' costruttore
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' calcolo della media dei voti
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' deviazione standard
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' metodi dell'interfaccia Istats
Public Function moyenne() As Double Implements Istats.moyenne
' calcola la media dei voti
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' restituisce la deviazione standard
Return _écartType
End Function
End Class
La media _moyenne e la deviazione standard _ecartType vengono calcolate al momento della creazione dell’oggetto. Pertanto, i metodi moyenne e écartType devono semplicemente restituire il valore degli attributi _moyenne e _ecartType. Entrambi i metodi restituiscono -1 se l'array degli studenti è vuoto.
La seguente classe di test:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Module test
Sub Main()
' alcuni studenti e voti
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' che vengono salvati in un oggetto «note»
Dim anglais As New notes("anglais", ELEVES)
' e che vengono visualizzati
Console.Out.WriteLine((anglais.ToString))
' idem con media e deviazione standard
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
fornisce i seguenti risultati:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
La classe notesStats avrebbe potuto benissimo implementare i metodi moyenne e écartType per sé stessa senza indicare che implementava l'interfaccia Istats. Qual è l’utilità delle interfacce? È la seguente: una funzione può accettare come parametro formale un dato del tipo di un’interfaccia I. Qualsiasi oggetto di una classe C che implementi l’interfaccia I potrà quindi essere un parametro effettivo di tale funzione. Consideriamo il seguente esempio:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' un'interfaccia Iesempio
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' una prima classe
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'una seconda classe
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
L'interfaccia Iexemple definisce due metodi: ajouter e soustraire. Le classi classe1 e classe2 implementano questa interfaccia. Si noti che queste classi non fanno nient’altro, per semplificare l’esempio. Consideriamo ora il seguente esempio:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' classe di test
Module test
'calcolare
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' la funzione Main
Sub Main()
' creazione di due oggetti classe1 e classe2
Dim c1 As New classe1
Dim c2 As New classe2
' chiamate alla funzione statica «calcolare»
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
La funzione calculer accetta come parametro un elemento di tipo Iexemple. Potrà quindi ricevere per questo parametro sia un oggetto di tipo classe1 che di tipo classe2. Questo è ciò che avviene nella procedura Main con i seguenti risultati:
Si nota quindi che si tratta di una proprietà simile al polimorfismo visto per le classi. Se un insieme di classi Ci non collegate tra loro tramite ereditarietà (quindi non è possibile utilizzare il polimorfismo dell'ereditarietà) presenta un insieme di metodi con la stessa firma, può essere interessante raggruppare tali metodi in un'interfaccia I da cui erediterebbero tutte le classi interessate. Le istanze di queste classi Ci possono quindi essere utilizzate come parametri di funzioni che accettano un parametro di tipo I, c.a.d, ovvero funzioni che utilizzano solo i metodi degli oggetti Ci definiti nell’interfaccia I e non gli attributi e i metodi specifici delle diverse classi Ci. Si noti infine che l’ereditarietà delle interfacce può essere multipla, c.a.d. È possibile scrivere
Public Class classe
Implements I1,I2,...
dove le Ij sono interfacce.
3.6. Gli spazi dei nomi
Per scrivere una riga sullo schermo, utilizziamo l'istruzione
Se osserviamo la definizione della classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
scopriamo che fa parte dello spazio dei nomi System. Ciò significa che la classe Console dovrebbe essere indicata come System.Console e che in realtà dovremmo scrivere:
Si evita questo problema utilizzando una clausola imports:
Si dice che si importa lo spazio dei nomi System con la clausola imports. Quando il compilatore incontrerà il nome di una classe (in questo caso Console), cercherà di trovarla nei diversi spazi dei nomi importati dalle clausole imports. In questo caso troverà la classe Console nello spazio dei nomi System. Notiamo ora la seconda informazione associata alla classe Console:
Assembly: Mscorlib (in Mscorlib.dll)
Questa riga indica in quale “assembly” si trova la definizione della classe Console. Quando si compila al di fuori di Visual Studio.NET e si devono fornire i riferimenti delle diverse dll contenenti le classi da utilizzare, questa informazione può rivelarsi utile. Si ricorda che per fare riferimento ai dll necessari alla compilazione di una classe, si scrive:
Quando si crea una classe, è possibile inserirla all’interno di uno spazio dei nomi. Lo scopo di questi spazi dei nomi è quello di evitare conflitti di nomi tra le classi, ad esempio quando queste vengono commercializzate. Consideriamo due aziende, E1 e E2, che distribuiscono classi raggruppate rispettivamente nei file dll, E1.dll e E2.dll. Supponiamo che un cliente C acquisti questi due insiemi di classi, in cui entrambe le aziende hanno definito una classe denominata personne. Il cliente C compila un programma nel modo seguente:
Se il codice sorgente prog.vb utilizza la classe personne, il compilatore non saprà se deve prendere la classe personne da E1.dll o quella da E2.dll. Segnalerà un errore. Se l’azienda E1 si preoccupa di creare le proprie classi in uno spazio dei nomi denominato E1 e l’azienda E2 in uno spazio dei nomi denominato E2, le due classi personne si chiameranno quindi E1.personne e E2.personne. Il cliente dovrà utilizzare nelle proprie classi E1.personne oppure E2.personne, ma non personne. Lo spazio dei nomi consente di eliminare l’ambiguità. Per creare una classe in uno spazio dei nomi, si scrive:
Namespace istia.st
Public Class personne
' definizione della classe
...
end Class
end Namespace
A titolo di esempio, creiamo in uno spazio dei nomi la nostra classe personne studiata in precedenza. Sceglieremo istia.st come spazio dei nomi. La classe personne diventa:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
' creazione dello spazio dei nomi istia.st
Namespace istia.st
Public Class personne
' attributi
Private prenom As String
Private nom As String
Private age As Integer
' metodo
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' metodo
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Questa classe viene compilata in personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Ora utilizziamo la classe personne in una classe di test:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Imports istia.st
' pg test
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Per evitare di scrivere
Dim p1 As New istia.st.personne
abbiamo importato lo spazio dei nomi istia.st con una clausola imports:
Imports istia.st
Ora compiliamo il programma di prova:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
Questo genera un file test.exe che, una volta eseguito, fornisce i seguenti risultati:
3.7. L'esempio IMPOTS
Riprendiamo il calcolo dell'imposta già esaminato nel capitolo precedente e lo elaboriamo utilizzando una classe. Ricordiamo il problema:
Consideriamo il caso semplificato di un contribuente che deve dichiarare solo il proprio stipendio:
- si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
- se ha almeno tre figli, ha diritto a una mezza quota in più
- si calcola il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
- si calcola il suo coefficiente familiare QF = R / nbParts
- si calcola la sua imposta I. Consideriamo la seguente tabella:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31810 | 0,2 | 3309,5 |
39.970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55790 | 0,35 | 9316,5 |
92970 | 0,4 | 12106 |
127.860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172.040 | 0,55 | 30710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Ogni riga ha 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF<=campo1. Ad esempio, se QF=23000, si troverà la riga
L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la relazione QF<=campo1 non è mai soddisfatta, allora vengono utilizzati i coefficienti dell’ultima riga. In questo caso:
il che dà l'imposta I=0,65*R - 49062*nbParts.
La classe «impost» sarà definita come segue:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Public Class impot
' dati necessari per il calcolo dell'imposta
' provengono da una fonte esterna
Private limites(), coeffR(), coeffN() As Decimal
' costruttore
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' si verifica che le 3 tabelle abbiano le stesse dimensioni
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' va bene
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' calcolo dell'imposta
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calcolo del numero di quote
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' calcolo del reddito imponibile e del quoziente familiare
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' calcolo dell'imposta
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Viene creato un oggetto «imposta» con i dati necessari per il calcolo dell’imposta di un contribuente. Questa è la parte stabile dell’oggetto. Una volta creato l’oggetto, è possibile richiamare ripetutamente il suo metodo «calcolare», che calcola l’imposta del contribuente in base al suo stato civile (coniugato o meno), al numero di figli e al suo stipendio annuale. Un programma di test potrebbe essere il seguente:
' opzioni
Option Strict On
Option Explicit On
' spazi dei nomi
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' programma interattivo per il calcolo dell'imposta
' l'utente digita tre dati dalla tastiera: coniugato nbEnfants stipendio
' il programma visualizza quindi l’imposta da pagare
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tabelle di dati necessarie per il calcolo dell’imposta
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' creazione di un oggetto «imposta»
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' ciclo infinito
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' vengono richiesti i parametri per il calcolo dell'imposta
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' Cosa fare?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' verifica del numero di argomenti nella riga inserita
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' verifica della validità dei parametri
If Not erreur Then
' coniugato
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' stipendio
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' se i parametri sono corretti, si calcola l'imposta
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
Ecco un esempio di esecuzione del programma precedente:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





