Skip to content

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

    dim i, j as integer

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:

privé
Un campo privato (private) è accessibile solo dai metodi interni della classe
public
Un campo pubblico (public) è accessibile da qualsiasi funzione, definita o meno all’interno della classe
protégé
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:

dim p1 as personne p1
p1.prenom="Jean"
p1.nom="Dupont"
p1.age=30

sono illegali. Dobbiamo inizializzare un oggetto di tipo personne tramite un metodo pubblico. Questo è il ruolo del metodo initialise. Scriveremo:

dim p1 as personne
p1.initialise("Jean","Dupont",30)

La sintassi p1.initialise è corretta poiché initialise è di accesso pubblico.

3.1.4. L'operatore new

La sequenza di istruzioni

dim p1 as personne
p1.initialise("Jean","Dupont",30)

non è corretta. L'istruzione

dim p1 as personne

dichiara p1 come riferimento a un oggetto di tipo personne. Questo oggetto non esiste ancora e quindi p1 non è inizializzato. È come se si scrivesse:

dim p1 as personne=nothing

dove si indica esplicitamente con la parola chiave nothing che la variabile p1 non fa ancora riferimento ad alcun oggetto. Quando poi si scrive

p1.initialise("Jean","Dupont",30)

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:

dim p1 as personne=new personne()

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

p1.initialise("Jean","Dupont",30)

è 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:

p1.initialise("Jean","Dupont",30)

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
dos>personne1
Jean,Dupont,30

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

Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End module
personne2.vb

' opzioni
Option Explicit On 
Option Strict On

' spazi dei nomi
Imports System

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    'initialise

    ' metodo
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub    'identifie
End Class 'personne

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à:

vbc /r:fic1.dll /r:fic2.dll ... fichierSource.vb

L'esecuzione del programma test1.exe fornisce i seguenti risultati:

dos>test
Jean,Dupont,30

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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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:

    dim p1 as personne
    p1=new personne

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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

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

dim p1 as personne=new personne("Jean","Dupont",30)

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:

    p1=nothing

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:

dim p2 as personne=p1

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:

dim p3 as personne =new personne(p1);

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

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

        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))

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

        P.age = 56

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)
âge (-4) invalide

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:

    Private Shared _nbPersonnes As Long = 0

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:

    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property

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:

    Nombre de personnes créées : 2

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:

Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66

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:

    Oi=Oj avec j>i

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

Oi=Oj

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

    dim p as personne
    ...
    affiche(p);

che

    dim e as enseignant
    ...
    affiche(e);

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:

    public class personne

si scrive implicitamente:

    public class personne 
         inherits object

La classe object definisce un metodo virtuale ToString:

Image

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]:

Count
attributo che indica il numero di elementi dell’elenco
Add(Object)
metodo che consente di aggiungere un oggetto all’elenco
Item(Integer i)
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à:

ToString
restituisce una stringa di caratteri che «rappresenta» il contenuto dell’elenco
Add(personne)
metodo che consente di aggiungere una persona all'elenco
Item(Integer i)
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

dim p as personne=l("nom")

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:

p1=personne(paul,10)
p2=personne(paul,10)
p1=personne(paul,10)
p2=personne(nicole,30)

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:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

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:

Public Interface Istats
    Function moyenne() As Double
    Function écartType() As Double
End Interface

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:

17
21
127
201

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

Console.Out.WriteLine(...)

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:

System.Console.Out.WriteLine(...)

Si evita questo problema utilizzando una clausola imports:

imports System
...
Console.Out.WriteLine(...)

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:

vbc /r:fic1.dll /r:fic2.dll ... prog.vb

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:

vbc /r:E1.dll /r:E2.dll prog.vb

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>dir
11/03/2004  18:27               610 personne.vb
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>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb

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:

Jean,Dupont,30

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

    24740        0.15        2072.5

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:

    0                0.65        49062

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:

dir>dir
12/03/2004  18:20             1 483 impots.vb
12/03/2004  18:21             2 805 test.vb
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 :