3. Classi, strutture, interfacce
3.1. Esempi di oggetti
3.1.1. Panoramica generale
Esploreremo ora la programmazione orientata agli oggetti attraverso alcuni esempi. Un oggetto è un'entità che contiene dati che ne definiscono lo stato (chiamati proprietà) e funzioni (chiamate metodi). Un oggetto viene creato sulla base di un modello chiamato classe:
Public Class c1
' attributes
Private p1 As type1
Private p2 As type2
....
' method
Public Sub m1(....)
...
End Sub
' method
Public Function m2(...)
....
End Function
End Class
Partendo dalla classe precedente C1, possiamo creare molti 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 il proprio stato. Per analogia, la dichiarazione
crea due oggetti (il termine non è corretto in questo contesto) di tipo (classe) Integer. La loro unica proprietà è il loro valore. Se O1 è un oggetto di tipo C1, O1.p1 si riferisce alla proprietà p1 di O1 e O1.m1 al metodo m1 di O1. Consideriamo un primo modello a oggetti: la classe Person.
3.1.2. Definizione della classe Person
La definizione della classe Person sarà la seguente:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qui abbiamo la definizione di una classe, che è un tipo di dati. Quando creiamo variabili di questo tipo, le chiamiamo oggetti o istanze di classe. Una classe è quindi un modello da cui vengono costruiti gli oggetti. I membri o i campi di una classe possono essere dati (attributi), metodi (funzioni) o proprietà. Le proprietà sono metodi speciali utilizzati per recuperare o impostare il valore degli attributi di un oggetto. Questi campi possono essere accompagnati da una delle tre parole chiave seguenti:
Un campo privato è accessibile solo dai metodi interni della classe | |
Un campo pubblico è accessibile da qualsiasi funzione, indipendentemente dal fatto che sia definita all'interno della classe | |
Un campo protetto è accessibile solo dai metodi interni della classe o da un oggetto derivato (vedere il concetto di ereditarietà più avanti). |
In genere, 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
- può chiamare i metodi pubblici dell'oggetto, in particolare quelli che consentono l'accesso ai suoi dati privati.
La sintassi per dichiarare 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 in cui vengono dichiarati gli attributi privati, protetti e pubblici è arbitrario.
3.1.3. Il metodo initialize
Torniamo alla nostra classe [person] dichiarata come:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Qual è il ruolo del metodo initialize? Poiché lastName, firstName e age sono dati privati della classe Person, le istruzioni:
non sono validi. Dobbiamo inizializzare un oggetto di tipo Person tramite un metodo pubblico. Questo è il ruolo del metodo initialize. Scriveremo:
La sintassi p1.initialize è valida perché initialize è pubblico.
3.1.4. L'operatore new
La sequenza di istruzioni
non è corretto. L'istruzione
dichiara p1 come riferimento a un oggetto di tipo persona. Questo oggetto non esiste ancora, quindi p1 non è inizializzato. È come se scrivessimo:
dove indichiamo esplicitamente con la parola chiave nothing che la variabile p1 non fa ancora riferimento a nessun oggetto. Quando poi scriviamo
chiamiamo il metodo initialize dell'oggetto a cui fa riferimento p1. Tuttavia, questo oggetto non esiste ancora e il compilatore segnalerà un errore. Per far sì che p1 faccia riferimento a un oggetto, è necessario scrivere:
Questo crea un oggetto Person non inizializzato: gli attributi name e first_name, che sono riferimenti a oggetti String, avranno il valore nothing, mentre age avrà il valore 0. Esiste quindi un'inizializzazione predefinita. Ora che p1 fa riferimento a un oggetto, l'istruzione di inizializzazione per questo oggetto
è valida.
3.1.5. La parola chiave Me
Diamo un'occhiata al codice del metodo *Initialize*:
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.firstName = P significa che alla proprietà firstName dell'oggetto corrente (Me) viene assegnato il valore P. La parola chiave Me si riferisce all'oggetto corrente: quello in cui viene eseguito il metodo. Come lo sappiamo? Vediamo come viene inizializzato l'oggetto a cui fa riferimento p1 nel programma chiamante:
Viene chiamato il metodo initialize dell'oggetto p1. Quando l'oggetto Me viene referenziato all'interno di questo metodo, in realtà si riferisce all'oggetto p1. Il metodo initialize 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 quell'oggetto, la notazione Me.A è implicita. Deve essere utilizzata esplicitamente quando c'è un conflitto di identificatori. Questo è il caso dell'istruzione:
Me.age=age;
dove age si riferisce sia a un attributo dell'oggetto corrente sia al parametro age ricevuto dal metodo. L'ambiguità deve quindi essere risolta facendo riferimento all'attributo age come Me.age.
3.1.6. Un programma di prova
Ecco un breve programma di prova:
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
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 classe compilato (assembly)
Si noti che nell'esempio precedente, nel nostro programma di test sono presenti due classi: la classe person e la classe test1. Esiste un altro modo di procedere:
-
Compilare la classe Person in un file separato chiamato assembly. Questo file ha estensione .dll
-
Compiliamo la classe test1 facendo riferimento all'assembly che contiene la classe person.
I due file sorgente diventano i seguenti:
test.vb | |
person2.vb | |
La classe Person viene compilata dalla 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 prodotto un file denominato personne2.dll. È l'opzione di compilazione /t:library che specifica la creazione di 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à determinate classi nel file personne2.dll. Quando trova un riferimento alla classe Person nel file sorgente test.vb — una classe non dichiarata nel file sorgente test.vb — cercherà la classe Person nei file .dll a cui fa riferimento l'opzione /r. Troverà la classe Person qui nell'assembly Person2.dll. Avremmo potuto includere altre classi in questo assembly. Per utilizzare più file di classe compilati durante la compilazione, scriveremmo:
L'esecuzione del programma test1.exe produce i seguenti risultati:
3.1.8. Un altro metodo inizializza
Continuiamo con la classe Person e aggiungiamo il seguente metodo:
' method
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Ora abbiamo due metodi denominati Initialize: ciò è valido purché accettino parametri diversi. È proprio questo il caso. 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 Initialize ha accesso diretto agli attributi dell'oggetto P anche se sono di tipo privato. Questo è sempre vero: un oggetto O1 della classe C ha sempre accesso agli attributi degli oggetti della stessa classe C. Ecco un test della nuova classe Person, che è stata compilata in Person.dll come spiegato in precedenza:
' options
Option Explicit On
Option Strict On
' namespaces
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 Person
Un costruttore è una procedura denominata New che viene chiamata quando viene creato l'oggetto. Viene generalmente utilizzato per inizializzare l'oggetto. Se una classe ha un costruttore che accetta n argomenti argi, la dichiarazione e l'inizializzazione di un oggetto di quella classe possono essere effettuate come segue:
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, uno di questi deve essere utilizzato per creare un oggetto di quella classe. Se una classe C non ha costruttori, ha un costruttore predefinito, ovvero il costruttore senza parametri: public New(). Gli attributi dell'oggetto vengono quindi inizializzati con i valori predefiniti. Questo è ciò che è accaduto nei programmi precedenti, dove abbiamo scritto:
Creiamo due costruttori per la nostra classe Person:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' the person class
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' manufacturers
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
' object initialization methods
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Entrambi i nostri costruttori richiamano semplicemente i corrispondenti metodi initialise*. Si noti che quando, ad esempio, la notazione initialise(P) compare in un costruttore, il compilatore la traduce in Me.initialise(P)*. Nel costruttore, il metodo initialise viene quindi richiamato per operare sull'oggetto a cui fa riferimento *Me*, ovvero l'oggetto corrente, quello che si sta costruendo. Ecco un breve programma di prova:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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. Riferimenti agli oggetti
Stiamo ancora utilizzando la stessa classe Person. Il programma di test diventa il seguente:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 references the same object as p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 references an object that will be a copy of the object referenced by p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' change the state of the object referenced by p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' as p2=p1, the object referenced by p2 must have changed state
Console.Out.Write("p2=")
p2.identifie()
' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
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 con
p1 fa riferimento all'oggetto Person("Jean", "Dupont", 30) ma non è l'oggetto stesso. In C, diremmo che si tratta di un puntatore, ovvero l'indirizzo dell'oggetto creato. Se poi scriviamo:
Non è l'oggetto person("Jean","Dupont",30) che viene modificato; è il riferimento p1 che cambia valore. L'oggetto person("Jean","Dupont",30) andrà "perso" se non è referenziato da nessun'altra variabile.
Quando scriviamo:
inizializziamo il puntatore p2: esso "punta" allo stesso oggetto (fa riferimento allo stesso oggetto) del puntatore p1. Pertanto, se modifichiamo l'oggetto "indicato" (o a cui fa riferimento) da p1, modifichiamo anche quello a cui fa riferimento p2.
Quando scriviamo:
Viene creato un nuovo oggetto, che è una copia dell'oggetto a cui fa riferimento p1. Questo nuovo oggetto sarà referenziato da p3. Se si modifica l'oggetto "indicato" (o referenziato) da p1, non si modifica in alcun modo quello referenziato da p3. Questo è quanto dimostrano i risultati.
3.1.11. Oggetti temporanei
In un'espressione, è possibile chiamare esplicitamente il costruttore di un oggetto: l'oggetto viene creato, ma non è possibile accedervi (ad esempio per modificarlo). Questo oggetto temporaneo viene creato allo scopo di valutare l'espressione e poi scartato. 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:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
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 Person in modo che visualizzino un messaggio:
' manufacturers
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 mostra la creazione progressiva dei due oggetti temporanei.
3.1.12. Metodi per la lettura e la scrittura degli attributi privati
Aggiungiamo alla classe Person i metodi necessari per leggere o modificare lo stato degli attributi degli oggetti:
Imports System
Public Class personne
' attributes
Private prenom As [String]
Private nom As [String]
Private age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accessors
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
'modifiers
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:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
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. Proprietà
Esiste un altro modo per accedere agli attributi di una classe: creando delle proprietà. Queste ci consentono di manipolare gli attributi privati come se fossero pubblici. Consideriamo la seguente classe Person, in cui i precedenti getter e setter sono stati sostituiti da proprietà di lettura-scrittura:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' class person
Public Class personne
' attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
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
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' properties
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)
' valid age?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Una proprietà consente di leggere (get) o impostare (set) il valore di un attributo. Nel nostro esempio, abbiamo anteposto ai nomi degli attributi un trattino basso (_) in modo che le proprietà abbiano gli stessi nomi degli attributi primitivi. Questo perché una proprietà non può avere lo stesso nome dell'attributo che gestisce; altrimenti, ci sarebbe un conflitto di nomi all'interno della classe. Abbiamo quindi chiamato i nostri attributi _firstName, _lastName e _age e abbiamo modificato i costruttori e i metodi di conseguenza. Abbiamo quindi creato tre proprietà: lastName, firstName 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à. Può avere due metodi chiamati get e set. Il metodo get è solitamente responsabile di restituire il valore dell'attributo che gestisce (potrebbe restituire qualcos'altro; nulla gli impedisce di farlo). Il metodo set riceve un parametro chiamato value, che normalmente assegna all'attributo che gestisce. Può sfruttare questa opportunità per verificare la validità del valore ricevuto e, se necessario, generare un'eccezione se il valore non è valido. Questo è ciò che viene fatto qui per l'età.
Come vengono chiamati questi metodi get e set? Consideriamo il seguente programma di test:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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
stiamo cercando di recuperare i valori delle proprietà first_name, last_name e age della persona P. È il metodo get di queste proprietà che viene quindi chiamato e restituisce il valore dell'attributo che gestiscono.
Nell'istruzione
vogliamo impostare il valore della proprietà age. Viene quindi chiamato il metodo set di questa proprietà. Esso riceverà 56 come parametro di valore.
Una proprietà P di una classe C che definisce solo il metodo get è detta di sola lettura. Se c è un oggetto della classe C, l'operazione c.P=valore verrà rifiutata dal compilatore.
L'esecuzione del programma di test precedente produce 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 [person] creati in un'applicazione. Potremmo gestire noi stessi un contatore, ma rischiamo di dimenticare oggetti temporanei creati qua e là. Sembrerebbe più sicuro includere un'istruzione nei costruttori della classe [person] che incrementi un contatore. Il problema è passare un riferimento a questo contatore in modo che il costruttore possa incrementarlo: dobbiamo passare loro un nuovo parametro. Possiamo anche includere il contatore nella definizione della classe. Poiché si tratta di un attributo della classe stessa e non di un particolare oggetto di quella classe, lo dichiariamo in modo diverso utilizzando la parola chiave Shared:
Per farvi riferimento, scriviamo person._nbPeople per indicare che si tratta di un attributo della classe Person stessa. In questo caso, abbiamo creato un attributo privato a cui non è possibile accedere direttamente dall'esterno della classe. Creiamo quindi una proprietà pubblica per fornire l'accesso all'attributo di classe nbPeople. Per restituire il valore di nbPeople, il metodo Get di questa proprietà non necessita di un oggetto Person specifico: infatti, _nbPeople non è l'attributo di un oggetto specifico; è l'attributo dell'intera classe. Pertanto, abbiamo bisogno di una proprietà che sia dichiarata anche come Shared:
che, dall'esterno, verrà chiamata utilizzando la sintassi person.nbPeople. La proprietà è dichiarata come di sola lettura (ReadOnly) poiché non fornisce un metodo set. Ecco un esempio. La classe Person diventa la seguente:
Option Explicit On
Option Strict On
' namespaces
Imports System
' class
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifies
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
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:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
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à menzionato che, per impostazione predefinita, VB.NET passa i parametri effettivi della funzione per valore: i valori dei parametri effettivi vengono copiati nei parametri formali. Quando si ha a che fare con un oggetto, non lasciatevi fuorviare dalla comune convenzione linguistica di riferirsi a un "oggetto" piuttosto che a un "riferimento a un oggetto". Un oggetto viene manipolato solo tramite un riferimento (un puntatore) ad esso. Ciò che viene quindi passato a una funzione non è l'oggetto stesso, ma un riferimento a quell'oggetto. È quindi il valore del riferimento, e non il valore dell'oggetto stesso, che viene copiato nel parametro formale: non viene creato alcun nuovo oggetto. Se un riferimento a un oggetto R1 viene passato a una funzione, verrà copiato nel corrispondente parametro formale R2. Pertanto, i riferimenti R2 e R1 puntano allo stesso oggetto. Se la funzione modifica l'oggetto puntato da R2, ovviamente modifica anche quello a cui fa riferimento R1, poiché sono lo stesso.
![]() |
Ciò è illustrato dal seguente esempio:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' one person p1
Dim p1 As New personne("Jean", "Dupont", 30)
' p1 display
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' p1 display
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' display person P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' display 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
Possiamo vedere che viene creato un solo oggetto: quello della persona p1 nella procedura Main, e che l'oggetto è stato effettivamente modificato dalla funzione modify.
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:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a table of people
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)
' display
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 friends(2) As Person crea un array di 3 elementi di tipo Person. Questi 3 elementi vengono inizializzati qui con il valore Nothing, il che significa che non fanno riferimento ad alcun oggetto. Ancora una volta, in senso tecnico, ci riferiamo a un "array di oggetti" quando in realtà 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: questo deve essere fatto in un secondo momento. Si ottengono i seguenti risultati:
3.2. Eredità per esempio
3.2.1. Panoramica
Qui discutiamo il concetto di ereditarietà. Lo scopo dell'ereditarietà è quello di "personalizzare" una classe esistente in modo che soddisfi le nostre esigenze. Supponiamo di voler creare una classe Insegnante: un insegnante è un tipo specifico di persona. Ha attributi che altre persone non hanno: la materia che insegna, per esempio. Ma ha anche gli attributi di qualsiasi persona: nome, cognome ed età. Un insegnante è quindi un membro a pieno titolo della classe Persona, ma possiede attributi aggiuntivi. Anziché scrivere una classe Insegnante da zero, preferiremmo partire dalla classe Persona esistente e adattarla alle caratteristiche specifiche degli insegnanti. Questo è ciò che il concetto di ereditarietà ci permette di fare. Per esprimere che la classe Insegnante eredita le proprietà della classe Persona, scriviamo:
Public Class enseignant
Inherits personne
Si noti la sintassi specifica su due righe. La classe Person viene chiamata classe padre (o base), mentre la classe Teacher viene chiamata classe derivata (o figlia). Un oggetto Teacher possiede tutte le caratteristiche di un oggetto Person: ha gli stessi attributi e metodi. Questi attributi e metodi della classe padre non vengono ripetuti nella definizione della classe figlia; specifichiamo semplicemente gli attributi e i metodi aggiunti dalla classe figlia. Supponiamo che la classe Person sia definita come segue:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' class person
Public Class personne
' class attributes
Private Shared _nbPersonnes As Long = 0
' instance attributes
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' manufacturers
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' one more person
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' follow-up
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' class property
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' instance properties
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)
' valid age?
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 Identify è stato sostituito dalla proprietà Identity di sola lettura, che identifica la persona. Creiamo una classe Teacher che eredita dalla classe Person:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
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 Teacher aggiunge quanto segue ai metodi e agli attributi della classe Person:
- un attributo `section`, che indica il numero della sezione a cui appartiene l'insegnante all'interno del corpo docente (approssimativamente una sezione per materia)
- un nuovo costruttore che inizializza tutti gli attributi di un insegnante
La dichiarazione
Public Class enseignant
Inherits personne
indica che la classe Insegnante deriva dalla classe Persona.
3.2.2. Creazione di un oggetto Insegnante
Il costruttore della classe Teacher è il seguente:
' manufacturer
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
' follow-up
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, età e sezione. Deve passare tre di essi (P, N, età) alla sua classe base, in questo caso la classe Person. Sappiamo che questa classe ha un costruttore Person(string, string, int) che ci permetterà di creare un oggetto Person utilizzando i parametri passati (P, N, age). La classe [Teacher] passa i parametri (P, N, age) alla sua classe base come segue:
MyBase.New(P, N, age)
Una volta che la classe base è stata costruita, la costruzione dell'oggetto Teacher 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 autocostruirsi
- utilizza gli altri parametri per inizializzare i propri attributi
Avremmo potuto scegliere di 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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Questo è impossibile. La classe Person ha dichiarato i suoi tre campi — _firstName, _lastName e _age — come privati. Solo gli oggetti della stessa classe hanno accesso diretto a questi campi. Tutti gli altri oggetti, compresi gli oggetti figli come in questo caso, devono utilizzare metodi pubblici per accedervi. La situazione sarebbe stata diversa se la classe Person avesse dichiarato i tre campi come protetti: in tal caso avrebbe consentito alle classi derivate di avere accesso diretto ai tre campi. Nel nostro esempio, l'utilizzo del costruttore della classe padre era quindi la soluzione corretta ed è l'approccio standard: quando si costruisce un oggetto figlio, si chiama prima il costruttore dell'oggetto padre e poi si completano le inizializzazioni specifiche dell'oggetto figlio (sezione nel nostro esempio).
Compiliamo le classi Person e Teacher 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 teacher, abbiamo dovuto fare riferimento al file person.dll, che contiene la classe person. Proviamo un primo programma di test:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Questo programma crea semplicemente un oggetto Teacher (new) e ne identifica l'identità. La classe Teacher non dispone di un metodo Identity, ma la sua classe padre ne possiede uno, che è anche pubblico: tramite ereditarietà, diventa un metodo pubblico della classe Teacher. 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)
Possiamo notare che:
- un oggetto persona è stato costruito prima dell'oggetto insegnante
- l'identità ottenuta è quella dell'oggetto persona
3.2.3. Sovraccaricare un metodo o una proprietà
Nell'esempio precedente, avevamo l'identità della persona come parte dell'insegnante, ma mancano alcune informazioni specifiche della classe Teacher (la sezione). Dobbiamo quindi scrivere una proprietà che ci consenta di identificare l'insegnante:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class enseignant
Inherits personne
' attributes
Private _section As Integer
' manufacturer
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
' follow-up
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' property section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' overload property identity
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
Il metodo identity della classe Teacher si basa sul metodo identity della sua classe padre (MyBase.identity) per visualizzare la parte "person" e poi la integra con il campo _section, che è specifico della classe Teacher. Si noti la dichiarazione della proprietà identity:
Public Shadows ReadOnly Property identite() As String
che indica che la proprietà identity "nasconde" il metodo con lo stesso nome che potrebbe esistere nella classe padre. Consideriamo un oggetto Teacher E. Questo oggetto contiene al suo interno un oggetto Person:
![]() |
La proprietà identity è definita sia nella classe Teacher che nella sua classe padre Person. Nella classe figlia Teacher, la proprietà identity deve essere preceduta dalla parola chiave shadows per indicare che stiamo ridefinendo una nuova proprietà identity per la classe Teacher.
Public Shadows ReadOnly Property identite() As String
La classe Teacher ora ha due proprietà identity:
- quella ereditata dalla classe padre Person
- la propria
Se E è un oggetto Teacher, E.identity fa riferimento al metodo identity della classe Teacher. Si dice che la proprietà identity della classe padre è "sovrascritta" dalla proprietà identity 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 padre della sua classe padre, se esiste
- ecc…
L'ereditarietà consente quindi di ridefinire nella classe figlia metodi/proprietà con lo stesso nome presenti nella classe padre. È questo che permette alla classe figlia di adattarsi alle proprie esigenze. In combinazione con il polimorfismo, di cui parleremo tra poco, il sovraccarico di metodi/proprietà è il principale vantaggio dell'ereditarietà. Consideriamo lo stesso esempio di prima:
' options
Option Strict On
Option Explicit On
' namespaces
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. Polimorfismo
Consideriamo una gerarchia di classi: C0 C1 C2 … Cn dove Ci Cj indica che la classe Cj deriva dalla classe Ci. Ciò implica che la classe Cj possiede tutte le caratteristiche della classe Ci più alcune aggiuntive. Siano Oi oggetti di tipo Ci. È corretto scrivere:
Infatti, per eredità, la classe Cj possiede tutte le caratteristiche della classe Ci più alcune aggiuntive. Pertanto, un oggetto Oj di tipo Cj contiene al suo interno un oggetto di tipo Ci. L'operazione
significa che Oi è un riferimento all'oggetto di tipo Ci contenuto nell'oggetto Oj.
Il fatto che una variabile Oi di classe Ci possa in realtà fare riferimento non solo a un oggetto di 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)
Potremmo semplicemente scrivere
come
In quest'ultimo caso, il parametro formale di tipo persona nella funzione display riceverà un valore di tipo insegnante. Poiché il tipo insegnante deriva dal tipo persona, ciò è corretto.
3.2.5. Ridefinizione e polimorfismo
Completiamo la nostra procedura display:
Sub affiche(ByVal p As personne)
' displays identity of p
Console.Out.WriteLine(p.identite)
End Sub
Il metodo p.identity restituisce una stringa che identifica l'oggetto Person. Cosa succede nel nostro esempio precedente quando si ha a che fare con un oggetto Teacher:
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Diamo un'occhiata al seguente esempio:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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.identity ha eseguito la proprietà identity di una persona ogni volta: prima la persona contenuta nell'insegnante e, poi la persona p stessa. Non si è adattata all'oggetto effettivamente passato come parametro a display. Avremmo preferito avere l'identità completa dell'insegnante e. Per ottenere questo risultato, la notazione p.identity avrebbe dovuto fare riferimento alla proprietà identity dell'oggetto effettivamente puntato da p piuttosto che alla proprietà identity della parte "person" dell'oggetto effettivamente puntato da p. È possibile ottenere questo risultato dichiarando identity come proprietà sovrascrivibile nella classe base Person:
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à rifinabile o virtuale. Questa parola chiave può essere applicata anche ai metodi. Le classi figlie che ridefiniscono una proprietà o un metodo virtuale devono utilizzare la parola chiave overrides invece di shadows per qualificare la proprietà o il metodo ridefinito. Pertanto, nella classe enseignant, la proprietà identite è definita come segue:
' overload property identity
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Il programma di test:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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 siamo riusciti a ottenere l'identità completa dell'insegnante. Ora ridefiniamo un metodo anziché una proprietà. La classe Object è la classe "genitore" di tutte le classi VB.NET. Quindi, quando scriviamo:
stiamo implicitamente scrivendo:
La classe object definisce un metodo ToString virtuale:

Il metodo ToString restituisce il nome della classe a cui appartiene l'oggetto, come mostrato nell'esempio seguente:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test2
Sub Main()
' a teacher
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' a person
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
I risultati 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 sovrascritto il metodo ToString nelle classi Person e Teacher, possiamo comunque vedere che il metodo ToString della classe Object è ancora in grado di visualizzare il nome effettivo della classe dell'oggetto. Sovrascriviamo il metodo ToString nelle classi Person e Teacher:
' ToString
Public Overrides Function ToString() As String
' we return the identity property
Return identite
End Function
La definizione è la stessa in entrambe le classi. Consideriamo il seguente programma di test:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' a teacher
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' a person
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' poster
Sub affiche(ByVal p As personne)
' displays identity of 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. Definizione di un indicizzatore per una classe
Consideriamo la classe [ArrayList] predefinita 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):
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' an indexer
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
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
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]:
proprietà che restituisce il numero di elementi presenti nell'elenco | |
Metodo per aggiungere un oggetto alla lista | |
metodo che restituisce l'i-esimo elemento dell'elenco |
Si noti che per ottenere l'i-esimo elemento della lista, non abbiamo scritto [list.Item(i)] ma direttamente [list(i)], il che a prima vista sembra errato. Ciò è tuttavia possibile perché la classe [ArrayList] definisce una proprietà predefinita [Item] con una sintassi simile 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 [list(i)], verifica se la classe [ArrayList] ha definito una proprietà con la seguente firma:
Default Public Property Proc(ByVal var As Integer) As Type
Qui troverà la procedura [Item]. Traduce quindi la notazione [list(i)] in [list.Item(i)]. La proprietà [Item] è la proprietà indicizzata predefinita della classe [ArrayList]. L'esecuzione del programma precedente produce 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 chiamata [listOfPeople] che sia un elenco di persone, quindi un elenco specifico che sembra naturale derivare dalla classe [ArrayList]:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports System.Collections
' class listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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
' an indexer
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
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
La classe dispone dei seguenti metodi e proprietà:
restituisce una stringa che "rappresenta" il contenuto della lista | |
metodo per aggiungere una persona alla lista | |
proprietà indicizzata predefinita che restituisce la persona i-esima nell'elenco |
Esaminiamo le nuove funzionalità:
Viene creato un nuovo metodo [Add].
' to add a person to the list
Public Shadows Sub Add(ByVal p As Object)
' p must be a person
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 qui la parola chiave [Shadows] per indicare che la nuova procedura sostituisce quella nella classe padre. Il metodo [Add] della classe figlia verifica se l'oggetto aggiunto è effettivamente di tipo [person] o di un tipo derivato utilizzando la funzione [TypeOf]. Se così non fosse, viene generata un'eccezione utilizzando l'istruzione [Throw]. Se l'oggetto aggiunto è effettivamente di tipo [person], viene utilizzato il metodo [Add] della classe base per eseguire l'aggiunta.
Viene creata una proprietà indicizzata:
' an indexer
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, dobbiamo utilizzare la parola chiave [Shadows] per indicare che la nuova proprietà indicizzata [Index] "nasconderà" la proprietà [Item] della classe base. Si noti che ciò vale anche se le due proprietà non hanno lo stesso nome. La proprietà [Index] consente di fare riferimento alla persona all'indice i nell'elenco. Si basa sulla proprietà [Item] della classe base per accedere all'elemento all'indice i dell'oggetto [ArrayList] sottostante. Le modifiche al tipo vengono apportate per tenere conto del fatto che la proprietà [Item] funziona con elementi di tipo [Object], mentre la proprietà [Index] funziona con elementi di tipo [Person].
Infine, sovrascriviamo il metodo [ToString] della classe [ArrayList]:
' toString
Public Overrides Function ToString() As String
' render (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' browse the dynamic table
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' last element
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Questo metodo restituisce una stringa della forma "(e1,e2,...,en)", dove e1, e2, ..., en sono gli elementi della lista. Si noti la notazione [Me(i)], che si riferisce all'i-esimo elemento dell'oggetto corrente [Me]. Questa è la proprietà indicizzata predefinita utilizzata. Pertanto, [Me(i)] è equivalente a [Me.Index(i)].
Il codice della classe è contenuto nel file [lstpersonnes2.vb] e viene 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 prova:
' options
Option Explicit On
Option Strict On
' namespaces
Imports System
Imports System.Collections
Module test
Sub Main()
' create an empty list of people
Dim liste As listeDePersonnes = New listeDePersonnes
' creating people
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)
' list filling
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' list display
Console.Out.WriteLine(liste.ToString)
' add an object other than person
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
poi 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 di tipo [listOfPeople]. In questo caso, vogliamo indicizzare la lista l non in base al numero dell'elemento, ma al nome di una persona. Per farlo, definiamo una nuova proprietà indicizzata predefinita:
' another indexer
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' we search for the person with name 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 stiamo 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 PeopleList utilizzando una stringa N. Il risultato di PeopleList(N) è un numero intero. Questo numero intero sarà la posizione nell'elenco della persona con il nome N, oppure -1 se quella persona non è presente nell'elenco. Definiamo solo la proprietà get, impedendo così l'assegnazione listOfPeople("name")=value, 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 (anche se non ha la stessa firma).
Nel corpo del metodo get, percorriamo l'elenco delle persone alla ricerca del nome N passato come parametro. Se lo troviamo nella posizione i, restituiamo i; altrimenti, restituiamo -1.
Un nuovo programma di test potrebbe essere il seguente:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test pg
Module test
Sub Main()
' a list of people
Dim l As New listeDePersonnes
' add people
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' display
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' change item 1
l(1) = New personne("sylvie", "cachan", 5)
' display element 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' display list l
Console.Out.WriteLine(("l=" + l.ToString))
' people search
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 produce 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. 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
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
Nonostante le somiglianze nella sintassi, esistono differenze significative tra classi e strutture. Ad esempio, il concetto di ereditarietà non esiste nelle strutture. Se stiamo scrivendo una classe che non è destinata a derivare da un'altra, quali sono le differenze tra strutture e classi che ci aiuteranno a scegliere tra le due? Usiamo il seguente esempio per scoprirlo:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' spersonne structure
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' class cperson
Class cpersonne
Public nom As String
Public age As Integer
End Class
' a test module
Public Module test
Sub Main()
' a person p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' a p2 person
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 is modified
sp2.nom = "nicole"
sp2.age = 30
' checking sp1 and sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' a cperson cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' a P2 person
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 is modified
cp2.nom = "nicole"
cp2.age = 30
' p1 and P2 verification
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Se eseguiamo questo programma, otteniamo 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)
Mentre nelle pagine precedenti di questo capitolo abbiamo utilizzato una classe Person, ora utilizziamo una PersonStructure:
' structure spersonne
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 è questa struttura stessa.
La dichiarazione
Dim cp1 As New cpersonne
crea un oggetto [cpersonne] (più o meno equivalente alla nostra struttura), e cp1 è quindi l'indirizzo (il riferimento) di quell'oggetto.
Riassumendo
- nel caso della struttura, il valore di sp1 è la struttura stessa
- nel caso di una classe, il valore di p1 è l'indirizzo dell'oggetto creato
![]() |
![]() |
Quando nel programma scriviamo
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
funziona in modo diverso. Il valore di cp1 viene copiato in cp2, ma poiché questo valore è in realtà l'indirizzo dell'oggetto, l'oggetto stesso non viene duplicato. Ha semplicemente due riferimenti che puntano ad esso:
![]() |
Nel caso della struttura, se modifichiamo il valore di sp2, non modifichiamo il valore di sp1, come dimostra il programma. Nel caso dell'oggetto, se modifichiamo l'oggetto puntato da cp2, viene modificato anche quello puntato da cp1 poiché sono lo stesso. Anche questo è dimostrato dai risultati del programma.
Da queste spiegazioni possiamo quindi concludere 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 si rivela molto simile a una classe, come mostra il seguente nuovo esempio:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure person
Structure personne
' attributes
Private _nom As String
Private _age As Integer
' properties
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
' Manufacturer
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
' a test module
Module test
Sub Main()
' one person p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' one person p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 is modified
p2.nom = "nicole"
p2.age = 30
' checking p1 and 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 qui tra una struttura e una 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. 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 verifica questa implementazione. Ecco, ad esempio, la definizione dell'interfaccia Istats:
Qualsiasi classe che implementi 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 [mean] e [standardDev] devono essere definiti nella classe C. Si consideri il seguente codice:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' manufacturer
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' class notes
Public Class notes
' attribute
Protected _matière As String
Protected _élèves() As élève
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' student & subject memorization
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
' concatenate all the notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'final note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' end
Return valeur
End Function
End Class
La classe notes raccoglie i voti di una classe in una materia:
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
Gli attributi sono dichiarati come protetti in modo da poter essere accessibili da una classe derivata. Il tipo student è 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
' manufacturer
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 avrà due attributi aggiuntivi: la media e la deviazione standard dei voti:
Public Class notesStats
Inherits notes
Implements Istats
' attributes
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 average e standardDeviation con le firme specificate nell'interfaccia Istats. La classe notesStats è la seguente:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributes
Private _moyenne As Double
Private _écartType As Double
' manufacturer
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' average score calculation
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
' standard deviation
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
' istats interface methods
Public Function moyenne() As Double Implements Istats.moyenne
' makes the average score
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' makes the standard deviation
Return _écartType
End Function
End Class
La media _mean e la deviazione standard _standardDev vengono calcolate al momento della creazione dell'oggetto. Pertanto, i metodi mean e standardDev restituiscono semplicemente i valori degli attributi _mean e _standardDev. Entrambi i metodi restituiscono -1 se l'array degli studenti è vuoto.
La seguente classe di test:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Module test
Sub Main()
' some students & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' recorded in a notes object
Dim anglais As New notes("anglais", ELEVES)
' and display
Console.Out.WriteLine((anglais.ToString))
' idem with mean and standard deviation
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 average e standardDev da sola senza specificare che implementava l'interfaccia Istats. Qual è lo scopo delle interfacce? È questo: una funzione può accettare come parametro formale un valore di tipo I. Qualsiasi oggetto della classe C che implementi l'interfaccia I può quindi essere un parametro effettivo di quella funzione. Consideriamo il seguente esempio:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' an Iexample interface
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
' a 1st class
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
'a 2nd class
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 Iexample definisce due metodi: add e subtract. Le classi Class1 e Class2 implementano questa interfaccia. Si noti che queste classi non fanno nient'altro, al fine di semplificare l'esempio. Consideriamo ora il seguente esempio:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' test class
Module test
'calculate
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
' the Hand function
Sub Main()
' creation of two objects class1 and class2
Dim c1 As New classe1
Dim c2 As New classe2
' static function calls calculate
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
La funzione calculate accetta come parametro un elemento di tipo Iexample. Può quindi ricevere come parametro un oggetto di tipo class1 o class2. Questo è ciò che viene fatto nella procedura Main con i seguenti risultati:
Possiamo quindi notare che questa proprietà è simile al polimorfismo di cui abbiamo parlato per le classi. Se un insieme di classi Ci, che non sono correlate tra loro tramite ereditarietà (e che quindi non possono avvalersi del polimorfismo basato sull'ereditarietà), condividono un insieme di metodi con la stessa firma, può essere utile raggruppare tali metodi in un'interfaccia I da cui tutte le classi interessate erediterebbero. Le istanze di queste classi Ci possono quindi essere utilizzate come parametri per funzioni che accettano un parametro di tipo I, ovvero funzioni che utilizzano solo i metodi degli oggetti Ci definiti nell'interfaccia I e non gli attributi e i metodi specifici delle varie classi Ci. Infine, si noti che l'ereditarietà dell'interfaccia può essere multipla, ovvero possiamo scrivere
Public Class classe
Implements I1,I2,...
dove le Ij sono interfacce.
3.6. Spazi dei nomi
Per scrivere una riga sullo schermo, usiamo l'istruzione
Se osserviamo la definizione della classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
vediamo che fa parte dello spazio dei nomi System. Ciò significa che la classe Console dovrebbe essere indicata come System.Console, e dovremmo quindi scrivere:
Possiamo evitare questo problema utilizzando una clausola imports:
Si dice che si importa lo spazio dei nomi System utilizzando la clausola imports. Quando il compilatore incontra un nome di classe (in questo caso, Console), lo cercherà nei vari spazi dei nomi importati dalle clausole imports. In questo caso, troverà la classe Console nello spazio dei nomi System. Ora notiamo 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 ed è necessario specificare i riferimenti alle varie DLL contenenti le classi che si devono utilizzare, questa informazione può essere utile. Ricordate che per fare riferimento alle DLL necessarie per compilare una classe, si scrive:
Quando si crea una classe, è possibile crearla 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 vengono vendute. Si considerino due aziende, E1 ed E2, che distribuiscono classi contenute rispettivamente nelle DLL E1.dll ed E2.dll. Supponiamo che un cliente C acquisti questi due set di classi, in cui entrambe le aziende hanno definito una classe Person. Il cliente C compila un programma come segue:
Se il file sorgente prog.vb utilizza la classe Person, il compilatore non saprà se utilizzare la classe Person contenuta in E1.dll o quella contenuta in E2.dll. Verrà segnalato un errore. Se l'azienda E1 si preoccupa di creare le proprie classi in uno spazio dei nomi chiamato E1 e l'azienda E2 in uno spazio dei nomi chiamato E2, le due classi Person saranno denominate E1.Person ed E2.Person. Il client deve utilizzare E1.Person o E2.Person nelle proprie classi, ma non Person. Lo spazio dei nomi risolve questa ambiguità. Per creare una classe in uno spazio dei nomi, si scrive:
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
Per questo esempio, creiamo la classe Person che abbiamo studiato in precedenza in uno spazio dei nomi. Sceglieremo istia.st come spazio dei nomi. La classe Person diventa:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
' creation of istia.st namespace
Namespace istia.st
Public Class personne
' attributes
Private prenom As String
Private nom As String
Private age As Integer
' method
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
' method
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Questa classe viene compilata in person.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 Person in una classe di test:
' options
Option Strict On
Option Explicit On
' namespaces
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, produce i seguenti risultati:
3.7. Esempio di calcolo delle imposte
Riprendiamo il calcolo delle imposte già trattato nel capitolo precedente e lo elaboriamo utilizzando una classe. Rivediamo il problema:
Consideriamo il caso semplificato di un contribuente che deve dichiarare solo il proprio stipendio:
- calcoliamo il numero di scaglioni fiscali per il dipendente come nbParts = nbEnfants / 2 + 1 se è celibe, e nbEnfants / 2 + 2 se è coniugato, dove nbEnfants è il numero di figli.
- Se ha almeno tre figli, riceve una quota aggiuntiva pari a metà
- Calcoliamo il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
- Calcoliamo il suo coefficiente familiare QF = R / nbParts
- calcoliamo la sua imposta I. Consideriamo la seguente tabella:
12620,0 | 0 | 0 |
13.190 | 0,05 | 631 |
15.640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31.810 | 0,2 | 3.309,5 |
39.970 | 0,25 | 4.900 |
48.360 | 0,3 | 6.898,5 |
55.790 | 0,35 | 9.316,5 |
92.970 | 0,4 | 12.106 |
127.860 | 0,45 | 16.754,5 |
151.250 | 0,50 | 23.147,5 |
172.040 | 0,55 | 30.710 |
195.000 | 0,60 | 39.312 |
0 | 0,65 | 49.062 |
Ogni riga ha 3 campi. Per calcolare l'imposta I, trova la prima riga in cui QF <= campo1. Ad esempio, se QF = 23.000, la riga trovata sarà
L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la condizione QF<=field1 non è mai soddisfatta, vengono utilizzati i coefficienti dell'ultima riga. Qui:
il che dà l'imposta I = 0,65 * R - 49062 * nbParts.
La classe **impot** sarà definita come segue:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Public Class impot
' data required for tax calculation
' come from an external source
Private limites(), coeffR(), coeffN() As Decimal
' manufacturer
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' check that the 3 arrays have the same size
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
' it's good
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' tAX CALCULATION
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calculating the number of shares
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
' calculation of taxable income & family quota
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' tAX CALCULATION
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 fiscale con i dati necessari per calcolare l'imposta di un contribuente. Questa è la parte stabile dell'oggetto. Una volta creato l'oggetto, il suo metodo **calculate** può essere chiamato ripetutamente per calcolare l'imposta del contribuente in base al suo stato civile (coniugato o meno), al numero di figli e allo stipendio annuale. Un programma di prova potrebbe apparire così:
' options
Option Strict On
Option Explicit On
' namespaces
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' interactive tax calculator
' the user enters three data points on the keyboard: married nbEnfants salary
' the program then displays the tax payable
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"
' data tables required for tax calculation
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}
' tax object creation
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
' infinite loop
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' tax calculation parameters are requested
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()
' anything to do?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' check the number of arguments in the input line
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
' checking parameter validity
If Not erreur Then
' married
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
' salary
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' if the parameters are correct - the tax is calculated
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 del programma precedente in azione:
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 :





