Skip to content

3. Classi, strutture, interfacce

3.1. Esempi di oggetti

3.1.1. Panoramica generale

Esploreremo ora la programmazione orientata agli oggetti attraverso alcuni esempi. Un oggetto è un'entità che contiene dati che ne definiscono lo stato (chiamati proprietà) e funzioni (chiamate metodi). Un oggetto viene creato sulla base di un modello chiamato classe:

Public Class c1
    ' attributes
    Private p1 As type1
    Private p2 As type2
....

    ' method
    Public Sub m1(....)
...
    End Sub    

    ' method
    Public Function m2(...)
....
    End Function    
End Class

Partendo dalla classe precedente C1, possiamo creare molti oggetti O1, O2,… Tutti avranno le proprietà p1, p2,… e i metodi m3, m4,… Ma avranno valori diversi per le loro proprietà pi, avendo così ciascuno il proprio stato. Per analogia, la dichiarazione

    dim i, j as integer

crea due oggetti (il termine non è corretto in questo contesto) di tipo (classe) Integer. La loro unica proprietà è il loro valore. Se O1 è un oggetto di tipo C1, O1.p1 si riferisce alla proprietà p1 di O1 e O1.m1 al metodo m1 di O1. Consideriamo un primo modello a oggetti: la classe Person.

3.1.2. Definizione della classe Person

La definizione della classe Person sarà la seguente:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Qui abbiamo la definizione di una classe, che è un tipo di dati. Quando creiamo variabili di questo tipo, le chiamiamo oggetti o istanze di classe. Una classe è quindi un modello da cui vengono costruiti gli oggetti. I membri o i campi di una classe possono essere dati (attributi), metodi (funzioni) o proprietà. Le proprietà sono metodi speciali utilizzati per recuperare o impostare il valore degli attributi di un oggetto. Questi campi possono essere accompagnati da una delle tre parole chiave seguenti:

privato
Un campo privato è accessibile solo dai metodi interni della classe
pubblico
Un campo pubblico è accessibile da qualsiasi funzione, indipendentemente dal fatto che sia definita all'interno della classe
protetto
Un campo protetto è accessibile solo dai metodi interni della classe o da un oggetto derivato (vedere il concetto di ereditarietà più avanti).

In genere, i dati di una classe sono dichiarati privati, mentre i suoi metodi e le sue proprietà sono dichiarati pubblici. Ciò significa che l'utente di un oggetto (il programmatore):

  • non avrà accesso diretto ai dati privati dell'oggetto
  • può chiamare i metodi pubblici dell'oggetto, in particolare quelli che consentono l'accesso ai suoi dati privati.

La sintassi per dichiarare una classe è la seguente:


public class classe
    private  donnée ou méthode ou propriété privée
    public  donnée ou méthode ou propriété publique
    protected  donnée ou méthode ou propriété protégée
end class

L'ordine in cui vengono dichiarati gli attributi privati, protetti e pubblici è arbitrario.

3.1.3. Il metodo initialize

Torniamo alla nostra classe [person] dichiarata come:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Qual è il ruolo del metodo initialize? Poiché lastName, firstName e age sono dati privati della classe Person, le istruzioni:

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

non sono validi. Dobbiamo inizializzare un oggetto di tipo Person tramite un metodo pubblico. Questo è il ruolo del metodo initialize. Scriveremo:

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

La sintassi p1.initialize è valida perché initialize è pubblico.

3.1.4. L'operatore new

La sequenza di istruzioni

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

non è corretto. L'istruzione

dim p1 as personne

dichiara p1 come riferimento a un oggetto di tipo persona. Questo oggetto non esiste ancora, quindi p1 non è inizializzato. È come se scrivessimo:

dim p1 as personne=nothing

dove indichiamo esplicitamente con la parola chiave nothing che la variabile p1 non fa ancora riferimento a nessun oggetto. Quando poi scriviamo

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

chiamiamo il metodo initialize dell'oggetto a cui fa riferimento p1. Tuttavia, questo oggetto non esiste ancora e il compilatore segnalerà un errore. Per far sì che p1 faccia riferimento a un oggetto, è necessario scrivere:

dim p1 as personne=new personne()

Questo crea un oggetto Person non inizializzato: gli attributi name e first_name, che sono riferimenti a oggetti String, avranno il valore nothing, mentre age avrà il valore 0. Esiste quindi un'inizializzazione predefinita. Ora che p1 fa riferimento a un oggetto, l'istruzione di inizializzazione per questo oggetto

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

è valida.

3.1.5. La parola chiave Me

Diamo un'occhiata al codice del metodo *Initialize*:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub    

L'istruzione Me.firstName = P significa che alla proprietà firstName dell'oggetto corrente (Me) viene assegnato il valore P. La parola chiave Me si riferisce all'oggetto corrente: quello in cui viene eseguito il metodo. Come lo sappiamo? Vediamo come viene inizializzato l'oggetto a cui fa riferimento p1 nel programma chiamante:

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

Viene chiamato il metodo initialize dell'oggetto p1. Quando l'oggetto Me viene referenziato all'interno di questo metodo, in realtà si riferisce all'oggetto p1. Il metodo initialize avrebbe potuto essere scritto anche come segue:


    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        prenom = P
        nom = N
        Me.age = age
    End Sub    

Quando un metodo di un oggetto fa riferimento a un attributo A di quell'oggetto, la notazione Me.A è implicita. Deve essere utilizzata esplicitamente quando c'è un conflitto di identificatori. Questo è il caso dell'istruzione:


Me.age=age;

dove age si riferisce sia a un attributo dell'oggetto corrente sia al parametro age ricevuto dal metodo. L'ambiguità deve quindi essere risolta facendo riferimento all'attributo age come Me.age.

3.1.6. Un programma di prova

Ecco un breve programma di prova:


Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' method
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

e i risultati ottenuti:

dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>personne1
Jean,Dupont,30

3.1.7. Utilizzo di un file di classe compilato (assembly)

Si noti che nell'esempio precedente, nel nostro programma di test sono presenti due classi: la classe person e la classe test1. Esiste un altro modo di procedere:

  • Compilare la classe Person in un file separato chiamato assembly. Questo file ha estensione .dll

  • Compiliamo la classe test1 facendo riferimento all'assembly che contiene la classe person.

I due file sorgente diventano i seguenti:

test.vb

Modulo test1
    Sub Main()
        Dim p1 As New Person
        p1.initialize("Jean", "Dupont", 30)
        p1.identify()
    Fine Sub
Fine Modulo
person2.vb

' opzioni
Opzione Esplicita On
Opzione Strict On

' spazi dei nomi
Importa System

Classe pubblica Person
    ' attributi
    Private nome As String
    Private cognome As String
    Età privato come Integer

    ' metodo
    Public Sub Inizializza(ByVal Nome As String, ByVal Cognome As String, ByVal Età As Integer)
        Me.firstName = P
        Me.lastName = N
        Me.age = age
    End Sub    'inizializza

    ' metodo
    Public Sub identifica()
        Console.Out.WriteLine((nome & "," & cognome & "," & età))
    End Sub    'identify
Fine Classe 'persona

La classe Person viene compilata dalla seguente istruzione:

dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.dll

La compilazione ha prodotto un file denominato personne2.dll. È l'opzione di compilazione /t:library che specifica la creazione di un file "assembly". Ora compiliamo il file test.vb:

dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004  16:50                  509 personne2.vb
24/02/2004  16:49                  143 test.vb
24/02/2004  16:50                3 584 personne2.dll
24/02/2004  16:51                3 072 test.exe

L'opzione di compilazione /r:personne2.dll indica al compilatore che troverà determinate classi nel file personne2.dll. Quando trova un riferimento alla classe Person nel file sorgente test.vb — una classe non dichiarata nel file sorgente test.vb — cercherà la classe Person nei file .dll a cui fa riferimento l'opzione /r. Troverà la classe Person qui nell'assembly Person2.dll. Avremmo potuto includere altre classi in questo assembly. Per utilizzare più file di classe compilati durante la compilazione, scriveremmo:

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

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

dos>test
Jean,Dupont,30

3.1.8. Un altro metodo inizializza

Continuiamo con la classe Person e aggiungiamo il seguente metodo:


    ' method
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub

Ora abbiamo due metodi denominati Initialize: ciò è valido purché accettino parametri diversi. È proprio questo il caso. Il parametro è ora un riferimento P a una Persona. Gli attributi della Persona P vengono quindi assegnati all'oggetto corrente (Me). Si noti che il metodo Initialize ha accesso diretto agli attributi dell'oggetto P anche se sono di tipo privato. Questo è sempre vero: un oggetto O1 della classe C ha sempre accesso agli attributi degli oggetti della stessa classe C. Ecco un test della nuova classe Person, che è stata compilata in Person.dll come spiegato in precedenza:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
Module test1
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne
        p2.initialise(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

e i relativi risultati:

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

3.1.9. Costruttori della classe Person

Un costruttore è una procedura denominata New che viene chiamata quando viene creato l'oggetto. Viene generalmente utilizzato per inizializzare l'oggetto. Se una classe ha un costruttore che accetta n argomenti argi, la dichiarazione e l'inizializzazione di un oggetto di quella classe possono essere effettuate come segue:


        dim objet as classe =new classe(arg1,arg2, ... argn)

oppure


        dim objet as classe

        objet=new classe(arg1,arg2, ... argn)

Quando una classe ha uno o più costruttori, uno di questi deve essere utilizzato per creare un oggetto di quella classe. Se una classe C non ha costruttori, ha un costruttore predefinito, ovvero il costruttore senza parametri: public New(). Gli attributi dell'oggetto vengono quindi inizializzati con i valori predefiniti. Questo è ciò che è accaduto nei programmi precedenti, dove abbiamo scritto:

    dim p1 as personne
    p1=new personne

Creiamo due costruttori per la nostra classe Person:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' the person class
Public Class personne
    ' attributes
    Private prenom As String
    Private nom As String
    Private age As Integer
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        initialise(P, N, age)
    End Sub
 
    Public Sub New(ByVal P As personne)
        initialise(P)
    End Sub
 
    ' object initialization methods
    Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub initialise(ByVal P As personne)
        prenom = P.prenom
        nom = P.nom
        Me.age = P.age
    End Sub
 
    ' method
    Public Sub identifie()
        Console.Out.WriteLine((prenom & "," & nom & "," & age))
    End Sub
End Class

Entrambi i nostri costruttori richiamano semplicemente i corrispondenti metodi initialise*. Si noti che quando, ad esempio, la notazione initialise(P) compare in un costruttore, il compilatore la traduce in Me.initialise(P)*. Nel costruttore, il metodo initialise viene quindi richiamato per operare sull'oggetto a cui fa riferimento *Me*, ovvero l'oggetto corrente, quello che si sta costruendo. Ecco un breve programma di prova:


' options 
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
        Dim p2 As New personne(p1)
        Console.Out.Write("p2=")
        p2.identifie()
    End Sub
End Module

e i risultati ottenuti:

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

3.1.10. Riferimenti agli oggetti

Stiamo ancora utilizzando la stessa classe Person. Il programma di test diventa il seguente:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' p1
        Dim p1 As New personne("Jean", "Dupont", 30)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' p2 references the same object as p1
        Dim p2 As personne = p1
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' p3 references an object that will be a copy of the object referenced by p1
        Dim p3 As New personne(p1)
        Console.Out.Write("p3=")
        p3.identifie()
 
        ' change the state of the object referenced by p1
        p1.initialise("Micheline", "Benoît", 67)
        Console.Out.Write("p1=")
        p1.identifie()
 
        ' as p2=p1, the object referenced by p2 must have changed state
        Console.Out.Write("p2=")
        p2.identifie()
 
        ' as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
        Console.Out.Write("p3=")
        p3.identifie()
    End Sub
End Module

I risultati ottenuti sono i seguenti:

p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30

Quando si dichiara la variabile p1 con

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

p1 fa riferimento all'oggetto Person("Jean", "Dupont", 30) ma non è l'oggetto stesso. In C, diremmo che si tratta di un puntatore, ovvero l'indirizzo dell'oggetto creato. Se poi scriviamo:

    p1=nothing

Non è l'oggetto person("Jean","Dupont",30) che viene modificato; è il riferimento p1 che cambia valore. L'oggetto person("Jean","Dupont",30) andrà "perso" se non è referenziato da nessun'altra variabile.

Quando scriviamo:

dim p2 as personne=p1

inizializziamo il puntatore p2: esso "punta" allo stesso oggetto (fa riferimento allo stesso oggetto) del puntatore p1. Pertanto, se modifichiamo l'oggetto "indicato" (o a cui fa riferimento) da p1, modifichiamo anche quello a cui fa riferimento p2.

Quando scriviamo:

dim p3 as personne =new personne(p1);

Viene creato un nuovo oggetto, che è una copia dell'oggetto a cui fa riferimento p1. Questo nuovo oggetto sarà referenziato da p3. Se si modifica l'oggetto "indicato" (o referenziato) da p1, non si modifica in alcun modo quello referenziato da p3. Questo è quanto dimostrano i risultati.

3.1.11. Oggetti temporanei

In un'espressione, è possibile chiamare esplicitamente il costruttore di un oggetto: l'oggetto viene creato, ma non è possibile accedervi (ad esempio per modificarlo). Questo oggetto temporaneo viene creato allo scopo di valutare l'espressione e poi scartato. Lo spazio di memoria che occupava verrà automaticamente recuperato in seguito da un programma chiamato "garbage collector", il cui ruolo è quello di recuperare lo spazio di memoria occupato da oggetti che non sono più referenziati dai dati del programma. Consideriamo il seguente nuovo programma di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p As New personne(New personne("Jean", "Dupont", 30))
        p.identifie()
    End Sub
End Module

e modifichiamo i costruttori della classe Person in modo che visualizzino un messaggio:


    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Console.Out.WriteLine("Constructeur personne(String, String, integer)")
        initialise(P, N, age)
    End Sub
 
    Public Sub New(ByVal P As personne)
        Console.Out.WriteLine("Constructeur personne(personne)")
        initialise(P)
    End Sub

Otteniamo i seguenti risultati:

dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30

che mostra la creazione progressiva dei due oggetti temporanei.

3.1.12. Metodi per la lettura e la scrittura degli attributi privati

Aggiungiamo alla classe Person i metodi necessari per leggere o modificare lo stato degli attributi degli oggetti:


Imports System
 
Public Class personne
 
    ' attributes
    Private prenom As [String]
    Private nom As [String]
    Private age As Integer

    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me.prenom = P
        Me.nom = N
        Me.age = age
    End Sub
 
    Public Sub New(ByVal P As personne)
        Me.prenom = P.prenom
        Me.nom = P.nom
        Me.age = P.age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((prenom + "," + nom + "," + age))
    End Sub
 
    ' accessors
    Public Function getPrenom() As [String]
        Return prenom
    End Function
 
    Public Function getNom() As [String]
        Return nom
    End Function
 
    Public Function getAge() As Integer
        Return age
    End Function
 
    'modifiers
    Public Sub setPrenom(ByVal P As [String])
        Me.prenom = P
    End Sub
 
    Public Sub setNom(ByVal N As [String])
        Me.nom = N
    End Sub
 
    Public Sub setAge(ByVal age As Integer)
        Me.age = age
    End Sub
End Class

Testiamo la nuova classe con il seguente programma:


' options
Option Strict On
Option Explicit On 

' namespaces
Imports System
 
' test pg
Public Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
        P.setAge(56)
        Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
    End Sub
End Module

e otteniamo i seguenti risultati:

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

3.1.13. Proprietà

Esiste un altro modo per accedere agli attributi di una classe: creando delle proprietà. Queste ci consentono di manipolare gli attributi privati come se fossero pubblici. Consideriamo la seguente classe Person, in cui i precedenti getter e setter sono stati sostituiti da proprietà di lettura-scrittura:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' class person
Public Class personne
 
    ' attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub
 
    ' properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Una proprietà consente di leggere (get) o impostare (set) il valore di un attributo. Nel nostro esempio, abbiamo anteposto ai nomi degli attributi un trattino basso (_) in modo che le proprietà abbiano gli stessi nomi degli attributi primitivi. Questo perché una proprietà non può avere lo stesso nome dell'attributo che gestisce; altrimenti, ci sarebbe un conflitto di nomi all'interno della classe. Abbiamo quindi chiamato i nostri attributi _firstName, _lastName e _age e abbiamo modificato i costruttori e i metodi di conseguenza. Abbiamo quindi creato tre proprietà: lastName, firstName e age. Una proprietà viene dichiarata come segue:


    Public Property nom() As Type
        Get
...
        End Get
        Set(ByVal Value As Type)
...
        End Set    
    End Property

dove Type deve essere il tipo dell'attributo gestito dalla proprietà. Può avere due metodi chiamati get e set. Il metodo get è solitamente responsabile di restituire il valore dell'attributo che gestisce (potrebbe restituire qualcos'altro; nulla gli impedisce di farlo). Il metodo set riceve un parametro chiamato value, che normalmente assegna all'attributo che gestisce. Può sfruttare questa opportunità per verificare la validità del valore ricevuto e, se necessario, generare un'eccezione se il valore non è valido. Questo è ciò che viene fatto qui per l'età.

Come vengono chiamati questi metodi get e set? Consideriamo il seguente programma di test:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim P As New personne("Jean", "Michelin", 34)
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        P.age = 56
        Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
        Try
            P.age = -4
        Catch ex As Exception
            Console.Error.WriteLine(ex.Message)
        End Try
    End Sub
End Module

Nell'istruzione

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

stiamo cercando di recuperare i valori delle proprietà first_name, last_name e age della persona P. È il metodo get di queste proprietà che viene quindi chiamato e restituisce il valore dell'attributo che gestiscono.

Nell'istruzione

        P.age = 56

vogliamo impostare il valore della proprietà age. Viene quindi chiamato il metodo set di questa proprietà. Esso riceverà 56 come parametro di valore.

Una proprietà P di una classe C che definisce solo il metodo get è detta di sola lettura. Se c è un oggetto della classe C, l'operazione c.P=valore verrà rifiutata dal compilatore.

L'esecuzione del programma di test precedente produce i seguenti risultati:

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 [person] creati in un'applicazione. Potremmo gestire noi stessi un contatore, ma rischiamo di dimenticare oggetti temporanei creati qua e là. Sembrerebbe più sicuro includere un'istruzione nei costruttori della classe [person] che incrementi un contatore. Il problema è passare un riferimento a questo contatore in modo che il costruttore possa incrementarlo: dobbiamo passare loro un nuovo parametro. Possiamo anche includere il contatore nella definizione della classe. Poiché si tratta di un attributo della classe stessa e non di un particolare oggetto di quella classe, lo dichiariamo in modo diverso utilizzando la parola chiave Shared:

    Private Shared _nbPersonnes As Long = 0

Per farvi riferimento, scriviamo person._nbPeople per indicare che si tratta di un attributo della classe Person stessa. In questo caso, abbiamo creato un attributo privato a cui non è possibile accedere direttamente dall'esterno della classe. Creiamo quindi una proprietà pubblica per fornire l'accesso all'attributo di classe nbPeople. Per restituire il valore di nbPeople, il metodo Get di questa proprietà non necessita di un oggetto Person specifico: infatti, _nbPeople non è l'attributo di un oggetto specifico; è l'attributo dell'intera classe. Pertanto, abbiamo bisogno di una proprietà che sia dichiarata anche come Shared:

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

che, dall'esterno, verrà chiamata utilizzando la sintassi person.nbPeople. La proprietà è dichiarata come di sola lettura (ReadOnly) poiché non fornisce un metodo set. Ecco un esempio. La classe Person diventa la seguente:


Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' class
Public Class personne
    ' class attributes
    Private Shared _nbPersonnes As Long = 0
 
    ' instance attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' one more person
        _nbPersonnes += 1
        Me._prenom = P
        Me._nom = N
        Me._age = age
    End Sub
 
    Public Sub New(ByVal P As personne)
        ' one more person
        _nbPersonnes += 1
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
    End Sub
 
    ' identifies
    Public Sub identifie()
        Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
    End Sub
 
    ' class property
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property
 
    ' instance properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
End Class

Con il seguente programma:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        Dim p1 As New personne("Jean", "Dupont", 30)
        Dim p2 As New personne(p1)
        Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
    End Sub
End Module

Si ottengono i seguenti risultati:

    Nombre de personnes créées : 2

3.1.15. Passaggio di un oggetto a una funzione

Abbiamo già menzionato che, per impostazione predefinita, VB.NET passa i parametri effettivi della funzione per valore: i valori dei parametri effettivi vengono copiati nei parametri formali. Quando si ha a che fare con un oggetto, non lasciatevi fuorviare dalla comune convenzione linguistica di riferirsi a un "oggetto" piuttosto che a un "riferimento a un oggetto". Un oggetto viene manipolato solo tramite un riferimento (un puntatore) ad esso. Ciò che viene quindi passato a una funzione non è l'oggetto stesso, ma un riferimento a quell'oggetto. È quindi il valore del riferimento, e non il valore dell'oggetto stesso, che viene copiato nel parametro formale: non viene creato alcun nuovo oggetto. Se un riferimento a un oggetto R1 viene passato a una funzione, verrà copiato nel corrispondente parametro formale R2. Pertanto, i riferimenti R2 e R1 puntano allo stesso oggetto. Se la funzione modifica l'oggetto puntato da R2, ovviamente modifica anche quello a cui fa riferimento R1, poiché sono lo stesso.

Ciò è illustrato dal seguente esempio:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("Jean", "Dupont", 30)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif avant modification : ")
        p1.identifie()
 
        ' modification p1
        modifie(p1)
 
        ' p1 display
        Console.Out.Write("Paramètre effectif après modification : ")
        p1.identifie()
    End Sub

    Sub modifie(ByVal P As personne)
        ' display person P
        Console.Out.Write("Paramètre formel avant modification : ")
        P.identifie()
        ' modification P
        P.prenom = "Sylvie"
        P.nom = "Vartan"
        P.age = 52
        ' display P
        Console.Out.Write("Paramètre formel après modification : ")
        P.identifie()
    End Sub
End Module

I risultati ottenuti sono i seguenti:

Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52

Possiamo vedere che viene creato un solo oggetto: quello della persona p1 nella procedura Main, e che l'oggetto è stato effettivamente modificato dalla funzione modify.

3.1.16. Un array di persone

Un oggetto è un dato come un altro e, in quanto tale, più oggetti possono essere raggruppati in un array:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
 
' test pg
Module test
    Sub Main()
        ' a table of people
        Dim amis(2) As personne
        amis(0) = New personne("Jean", "Dupont", 30)
        amis(1) = New personne("Sylvie", "Vartan", 52)
        amis(2) = New personne("Neil", "Armstrong", 66)
        ' display
        Console.Out.WriteLine("----------------")
        Dim i As Integer
        For i = 0 To amis.Length - 1
            amis(i).identifie()
        Next i
    End Sub
End Module

L'istruzione Dim friends(2) As Person crea un array di 3 elementi di tipo Person. Questi 3 elementi vengono inizializzati qui con il valore Nothing, il che significa che non fanno riferimento ad alcun oggetto. Ancora una volta, in senso tecnico, ci riferiamo a un "array di oggetti" quando in realtà si tratta semplicemente di un array di riferimenti a oggetti. La creazione dell'array di oggetti, che è esso stesso un oggetto, non crea alcun oggetto del tipo dei suoi elementi: questo deve essere fatto in un secondo momento. Si ottengono i seguenti risultati:

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

3.2. Eredità per esempio

3.2.1. Panoramica

Qui discutiamo il concetto di ereditarietà. Lo scopo dell'ereditarietà è quello di "personalizzare" una classe esistente in modo che soddisfi le nostre esigenze. Supponiamo di voler creare una classe Insegnante: un insegnante è un tipo specifico di persona. Ha attributi che altre persone non hanno: la materia che insegna, per esempio. Ma ha anche gli attributi di qualsiasi persona: nome, cognome ed età. Un insegnante è quindi un membro a pieno titolo della classe Persona, ma possiede attributi aggiuntivi. Anziché scrivere una classe Insegnante da zero, preferiremmo partire dalla classe Persona esistente e adattarla alle caratteristiche specifiche degli insegnanti. Questo è ciò che il concetto di ereditarietà ci permette di fare. Per esprimere che la classe Insegnante eredita le proprietà della classe Persona, scriviamo:


Public Class enseignant
    Inherits personne

Si noti la sintassi specifica su due righe. La classe Person viene chiamata classe padre (o base), mentre la classe Teacher viene chiamata classe derivata (o figlia). Un oggetto Teacher possiede tutte le caratteristiche di un oggetto Person: ha gli stessi attributi e metodi. Questi attributi e metodi della classe padre non vengono ripetuti nella definizione della classe figlia; specifichiamo semplicemente gli attributi e i metodi aggiunti dalla classe figlia. Supponiamo che la classe Person sia definita come segue:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' class person
Public Class personne
 
    ' class attributes
    Private Shared _nbPersonnes As Long = 0
 
    ' instance attributes
    Private _prenom As [String]
    Private _nom As [String]
    Private _age As Integer
 
 
    ' manufacturers
    Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P
        Me._nom = N
        Me._age = age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
 
    Public Sub New(ByVal P As personne)
        ' one more person
        _nbPersonnes += 1
        ' construction
        Me._prenom = P._prenom
        Me._nom = P._nom
        Me._age = P._age
        ' follow-up
        Console.Out.WriteLine("Construction personne(string, string, int)")
    End Sub
 
    ' class property
    Public Shared ReadOnly Property nbPersonnes() As Long
        Get
            Return _nbPersonnes
        End Get
    End Property
 
    ' instance properties
    Public Property prenom() As String
        Get
            Return _prenom
        End Get
        Set(ByVal Value As String)
            _prenom = Value
        End Set
    End Property
 
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = Value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            ' valid age?
            If Value >= 0 Then
                _age = Value
            Else
                Throw New Exception("âge (" & Value & ") invalide")
            End If
        End Set
    End Property
 
    Public ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property
End Class

Il metodo Identify è stato sostituito dalla proprietà Identity di sola lettura, che identifica la persona. Creiamo una classe Teacher che eredita dalla classe Person:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
End Class 

La classe Teacher aggiunge quanto segue ai metodi e agli attributi della classe Person:

  • un attributo `section`, che indica il numero della sezione a cui appartiene l'insegnante all'interno del corpo docente (approssimativamente una sezione per materia)
  • un nuovo costruttore che inizializza tutti gli attributi di un insegnante

La dichiarazione


Public Class enseignant
    Inherits personne

indica che la classe Insegnante deriva dalla classe Persona.

3.2.2. Creazione di un oggetto Insegnante

Il costruttore della classe Teacher è il seguente:


    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

La dichiarazione


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)

dichiara che il costruttore riceve quattro parametri: P, N, età e sezione. Deve passare tre di essi (P, N, età) alla sua classe base, in questo caso la classe Person. Sappiamo che questa classe ha un costruttore Person(string, string, int) che ci permetterà di creare un oggetto Person utilizzando i parametri passati (P, N, age). La classe [Teacher] passa i parametri (P, N, age) alla sua classe base come segue:


        MyBase.New(P, N, age)

Una volta che la classe base è stata costruita, la costruzione dell'oggetto Teacher prosegue con l'esecuzione del corpo del costruttore:


        Me._section = section

In sintesi, il costruttore di una classe derivata:

  • passa alla sua classe base i parametri necessari per autocostruirsi
  • utilizza gli altri parametri per inizializzare i propri attributi

Avremmo potuto scegliere di scrivere:


    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        Me._prenom = P
        Me._nom = N
        Me._age = age
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub    

Questo è impossibile. La classe Person ha dichiarato i suoi tre campi — _firstName, _lastName e _age — come privati. Solo gli oggetti della stessa classe hanno accesso diretto a questi campi. Tutti gli altri oggetti, compresi gli oggetti figli come in questo caso, devono utilizzare metodi pubblici per accedervi. La situazione sarebbe stata diversa se la classe Person avesse dichiarato i tre campi come protetti: in tal caso avrebbe consentito alle classi derivate di avere accesso diretto ai tre campi. Nel nostro esempio, l'utilizzo del costruttore della classe padre era quindi la soluzione corretta ed è l'approccio standard: quando si costruisce un oggetto figlio, si chiama prima il costruttore dell'oggetto padre e poi si completano le inizializzazioni specifiche dell'oggetto figlio (sezione nel nostro esempio).

Compiliamo le classi Person e Teacher in assembly:

dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>dir
25/02/2004  10:08                1 828 personne.vb
25/02/2004  10:11                  675 enseignant.vb
25/02/2004  10:12                  223 test.vb
25/02/2004  10:16                4 096 personne.dll
25/02/2004  10:16                3 584 enseignant.dll

Si noti che per compilare la classe figlia teacher, abbiamo dovuto fare riferimento al file person.dll, che contiene la classe person. Proviamo un primo programma di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

Questo programma crea semplicemente un oggetto Teacher (new) e ne identifica l'identità. La classe Teacher non dispone di un metodo Identity, ma la sua classe padre ne possiede uno, che è anche pubblico: tramite ereditarietà, diventa un metodo pubblico della classe Teacher. I risultati ottenuti sono i seguenti:

dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573

dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)

Possiamo notare che:

  • un oggetto persona è stato costruito prima dell'oggetto insegnante
  • l'identità ottenuta è quella dell'oggetto persona

3.2.3. Sovraccaricare un metodo o una proprietà

Nell'esempio precedente, avevamo l'identità della persona come parte dell'insegnante, ma mancano alcune informazioni specifiche della classe Teacher (la sezione). Dobbiamo quindi scrivere una proprietà che ci consenta di identificare l'insegnante:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class enseignant
    Inherits personne
    ' attributes
    Private _section As Integer
 
    ' manufacturer
    Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
        MyBase.New(P, N, age)
        Me._section = section
        ' follow-up
        Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
    End Sub
 
    ' property section
    Public Property section() As Integer
        Get
            Return _section
        End Get
        Set(ByVal Value As Integer)
            _section = Value
        End Set
    End Property
 
    ' overload property identity
    Public Shadows ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property
End Class

Il metodo identity della classe Teacher si basa sul metodo identity della sua classe padre (MyBase.identity) per visualizzare la parte "person" e poi la integra con il campo _section, che è specifico della classe Teacher. Si noti la dichiarazione della proprietà identity:


    Public Shadows ReadOnly Property identite() As String

che indica che la proprietà identity "nasconde" il metodo con lo stesso nome che potrebbe esistere nella classe padre. Consideriamo un oggetto Teacher E. Questo oggetto contiene al suo interno un oggetto Person:

La proprietà identity è definita sia nella classe Teacher che nella sua classe padre Person. Nella classe figlia Teacher, la proprietà identity deve essere preceduta dalla parola chiave shadows per indicare che stiamo ridefinendo una nuova proprietà identity per la classe Teacher.


    Public Shadows ReadOnly Property identite() As String

La classe Teacher ora ha due proprietà identity:

  • quella ereditata dalla classe padre Person
  • la propria

Se E è un oggetto Teacher, E.identity fa riferimento al metodo identity della classe Teacher. Si dice che la proprietà identity della classe padre è "sovrascritta" dalla proprietà identity della classe figlia. In generale, se O è un oggetto e M è un metodo, per eseguire il metodo O.M, il sistema cerca un metodo M nel seguente ordine:

  • nella classe dell'oggetto O
  • nella sua classe padre, se ne ha una
  • nella classe padre della sua classe padre, se esiste
  • ecc…

L'ereditarietà consente quindi di ridefinire nella classe figlia metodi/proprietà con lo stesso nome presenti nella classe padre. È questo che permette alla classe figlia di adattarsi alle proprie esigenze. In combinazione con il polimorfismo, di cui parleremo tra poco, il sovraccarico di metodi/proprietà è il principale vantaggio dell'ereditarietà. Consideriamo lo stesso esempio di prima:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System

Module test
    Sub Main()
        Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
    End Sub
End Module

I risultati ottenuti questa volta sono i seguenti:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)

3.2.4. Polimorfismo

Consideriamo una gerarchia di classi: C0 C1 C2 … Cn dove Ci Cj indica che la classe Cj deriva dalla classe Ci. Ciò implica che la classe Cj possiede tutte le caratteristiche della classe Ci più alcune aggiuntive. Siano Oi oggetti di tipo Ci. È corretto scrivere:

    Oi=Oj avec j>i

Infatti, per eredità, la classe Cj possiede tutte le caratteristiche della classe Ci più alcune aggiuntive. Pertanto, un oggetto Oj di tipo Cj contiene al suo interno un oggetto di tipo Ci. L'operazione

Oi=Oj

significa che Oi è un riferimento all'oggetto di tipo Ci contenuto nell'oggetto Oj.

Il fatto che una variabile Oi di classe Ci possa in realtà fare riferimento non solo a un oggetto di classe Ci, ma a qualsiasi oggetto derivato dalla classe Ci, è chiamato polimorfismo: la capacità di una variabile di fare riferimento a diversi tipi di oggetti. Facciamo un esempio e consideriamo la seguente funzione indipendente da qualsiasi classe:


    Sub affiche(ByVal p As personne)

Potremmo semplicemente scrivere

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

come

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

In quest'ultimo caso, il parametro formale di tipo persona nella funzione display riceverà un valore di tipo insegnante. Poiché il tipo insegnante deriva dal tipo persona, ciò è corretto.

3.2.5. Ridefinizione e polimorfismo

Completiamo la nostra procedura display:


    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub    

Il metodo p.identity restituisce una stringa che identifica l'oggetto Person. Cosa succede nel nostro esempio precedente quando si ha a che fare con un oggetto Teacher:


        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)

Diamo un'occhiata al seguente esempio:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

I risultati ottenuti sono i seguenti:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

L'esecuzione mostra che l'istruzione p.identity ha eseguito la proprietà identity di una persona ogni volta: prima la persona contenuta nell'insegnante e, poi la persona p stessa. Non si è adattata all'oggetto effettivamente passato come parametro a display. Avremmo preferito avere l'identità completa dell'insegnante e. Per ottenere questo risultato, la notazione p.identity avrebbe dovuto fare riferimento alla proprietà identity dell'oggetto effettivamente puntato da p piuttosto che alla proprietà identity della parte "person" dell'oggetto effettivamente puntato da p. È possibile ottenere questo risultato dichiarando identity come proprietà sovrascrivibile nella classe base Person:


    Public Overridable ReadOnly Property identite() As String
        Get
            Return "personne(" & _prenom & "," & _nom & "," & age & ")"
        End Get
    End Property

La parola chiave overridable rende *identite* una proprietà rifinabile o virtuale. Questa parola chiave può essere applicata anche ai metodi. Le classi figlie che ridefiniscono una proprietà o un metodo virtuale devono utilizzare la parola chiave overrides invece di shadows per qualificare la proprietà o il metodo ridefinito. Pertanto, nella classe enseignant, la proprietà identite è definita come segue:


    ' overload property identity
    Public Overrides ReadOnly Property identite() As String
        Get
            Return "enseignant(" & MyBase.identite & "," & _section & ")"
        End Get
    End Property

Il programma di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

produce quindi i seguenti risultati:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

Questa volta siamo riusciti a ottenere l'identità completa dell'insegnante. Ora ridefiniamo un metodo anziché una proprietà. La classe Object è la classe "genitore" di tutte le classi VB.NET. Quindi, quando scriviamo:

    public class personne

stiamo implicitamente scrivendo:

    public class personne 
         inherits object

La classe object definisce un metodo ToString virtuale:

Image

Il metodo ToString restituisce il nome della classe a cui appartiene l'oggetto, come mostrato nell'esempio seguente:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test2
    Sub Main()
        ' a teacher
        Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
        ' a person
        Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
    End Sub
End Module

I risultati sono i seguenti:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne

Si noti che, sebbene non abbiamo sovrascritto il metodo ToString nelle classi Person e Teacher, possiamo comunque vedere che il metodo ToString della classe Object è ancora in grado di visualizzare il nome effettivo della classe dell'oggetto. Sovrascriviamo il metodo ToString nelle classi Person e Teacher:


    ' ToString
    Public Overrides Function ToString() As String
        ' we return the identity property
        Return identite
    End Function

La definizione è la stessa in entrambe le classi. Consideriamo il seguente programma di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' a teacher
        Dim e As New enseignant("Lucile", "Dumas", 56, 61)
        affiche(e)
        ' a person
        Dim p As New personne("Jean", "Dupont", 30)
        affiche(p)
    End Sub
 
    ' poster
    Sub affiche(ByVal p As personne)
        ' displays identity of p
        Console.Out.WriteLine(p.identite)
    End Sub
End Module

I risultati dell'esecuzione sono i seguenti:

Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)

3.3. Definizione di un indicizzatore per una classe

Consideriamo la classe [ArrayList] predefinita nella piattaforma .NET. Questa classe consente di memorizzare oggetti in un elenco. Appartiene allo spazio dei nomi [System.Collections]. Nell'esempio seguente, utilizziamo questa classe per memorizzare un elenco di persone (in senso lato):


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports System.Collections
 
' class listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList
 
    ' to add a person to the list
    Public Overloads Sub Add(ByVal p As personne)
        MyBase.Add(p)
    End Sub
 
    ' an indexer
    Default Public Shadows Property Item(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' another indexer
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

Forniamo alcune informazioni su alcune proprietà e metodi della classe [ArrayList]:

Count
proprietà che restituisce il numero di elementi presenti nell'elenco
Add(Object)
Metodo per aggiungere un oggetto alla lista
Item(Integer i)
metodo che restituisce l'i-esimo elemento dell'elenco

Si noti che per ottenere l'i-esimo elemento della lista, non abbiamo scritto [list.Item(i)] ma direttamente [list(i)], il che a prima vista sembra errato. Ciò è tuttavia possibile perché la classe [ArrayList] definisce una proprietà predefinita [Item] con una sintassi simile alla seguente:


    Default Public Property Item(ByVal i As Integer) As Object
        Get
...
        End Get
        Set(ByVal Value As personne)
...
        End Set    
    End Property

Quando il compilatore incontra la notazione [list(i)], verifica se la classe [ArrayList] ha definito una proprietà con la seguente firma:


    Default Public Property Proc(ByVal var As Integer) As Type

Qui troverà la procedura [Item]. Traduce quindi la notazione [list(i)] in [list.Item(i)]. La proprietà [Item] è la proprietà indicizzata predefinita della classe [ArrayList]. L'esecuzione del programma precedente produce i seguenti risultati:

dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:34             3 584 lstpersonnes1.exe
12/03/2004  13:39               661 lstpersonnes1.vb
11/03/2004  18:26             4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)

Creiamo una classe chiamata [listOfPeople] che sia un elenco di persone, quindi un elenco specifico che sembra naturale derivare dalla classe [ArrayList]:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports System.Collections
 
' class listeDePersonnes
Public Class listeDePersonnes
    Inherits ArrayList
 
    ' to add a person to the list
    Public Shadows Sub Add(ByVal p As Object)
        ' p must be a person
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub
 
    ' an indexer
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property
 
    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function
End Class

La classe dispone dei seguenti metodi e proprietà:

ToString
restituisce una stringa che "rappresenta" il contenuto della lista
Add(persona)
metodo per aggiungere una persona alla lista
Item(Integer i)
proprietà indicizzata predefinita che restituisce la persona i-esima nell'elenco

Esaminiamo le nuove funzionalità:

Viene creato un nuovo metodo [Add].


    ' to add a person to the list
    Public Shadows Sub Add(ByVal p As Object)
        ' p must be a person
        If Not TypeOf (p) Is personne Then
            Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
        Else
            MyBase.Add(p)
        End If
    End Sub

Ne esiste già una nella classe padre [ArrayList] con la stessa firma, da qui la parola chiave [Shadows] per indicare che la nuova procedura sostituisce quella nella classe padre. Il metodo [Add] della classe figlia verifica se l'oggetto aggiunto è effettivamente di tipo [person] o di un tipo derivato utilizzando la funzione [TypeOf]. Se così non fosse, viene generata un'eccezione utilizzando l'istruzione [Throw]. Se l'oggetto aggiunto è effettivamente di tipo [person], viene utilizzato il metodo [Add] della classe base per eseguire l'aggiunta.

Viene creata una proprietà indicizzata:


    ' an indexer
    Default Public Shadows Property Index(ByVal i As Integer) As personne
        Get
            Return CType(MyBase.Item(i), personne)
        End Get
        Set(ByVal Value As personne)
            MyBase.Item(i) = Value
        End Set
    End Property

Nella classe base esiste già una proprietà predefinita denominata [Item] con la stessa firma. Pertanto, dobbiamo utilizzare la parola chiave [Shadows] per indicare che la nuova proprietà indicizzata [Index] "nasconderà" la proprietà [Item] della classe base. Si noti che ciò vale anche se le due proprietà non hanno lo stesso nome. La proprietà [Index] consente di fare riferimento alla persona all'indice i nell'elenco. Si basa sulla proprietà [Item] della classe base per accedere all'elemento all'indice i dell'oggetto [ArrayList] sottostante. Le modifiche al tipo vengono apportate per tenere conto del fatto che la proprietà [Item] funziona con elementi di tipo [Object], mentre la proprietà [Index] funziona con elementi di tipo [Person].

Infine, sovrascriviamo il metodo [ToString] della classe [ArrayList]:


    ' toString
    Public Overrides Function ToString() As String
        ' render (él1, él2, ..., éln)
        Dim liste As String = "("
        Dim i As Integer
        ' browse the dynamic table
        For i = 0 To (Count - 2)
            liste += "[" + Me(i).ToString + "]" + ","
        Next i        'for
        ' last element
        If Count <> 0 Then
            liste += "[" + Me(i).ToString + "]"
        End If
        liste += ")"
        Return liste
    End Function

Questo metodo restituisce una stringa della forma "(e1,e2,...,en)", dove e1, e2, ..., en sono gli elementi della lista. Si noti la notazione [Me(i)], che si riferisce all'i-esimo elemento dell'oggetto corrente [Me]. Questa è la proprietà indicizzata predefinita utilizzata. Pertanto, [Me(i)] è equivalente a [Me.Index(i)].

Il codice della classe è contenuto nel file [lstpersonnes2.vb] e viene compilato:

dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll

Viene creato un programma di prova:


' options
Option Explicit On 
Option Strict On
 
' namespaces
Imports System
Imports System.Collections
 
Module test
    Sub Main()
        ' create an empty list of people
        Dim liste As listeDePersonnes = New listeDePersonnes
        ' creating people
        Dim p1 As personne = New personne("paul", "chenou", 31)
        Dim p2 As personne = New personne("nicole", "chenou", 11)
        Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
        ' list filling
        liste.Add(p1)
        liste.Add(p2)
        liste.Add(e1)
        ' list display
        Console.Out.WriteLine(liste.ToString)
        ' add an object other than person
        Try
            liste.Add(4)
        Catch e As Exception
            Console.Error.WriteLine(e.Message)
        End Try
    End Sub
End Module

Viene compilato:

dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004  18:26             3 584 enseignant.dll
12/03/2004  15:50             3 584 lstpersonnes2.dll
12/03/2004  15:45               970 lstpersonnes2.vb
11/03/2004  18:26             4 096 personne.dll
12/03/2004  15:50             3 584 test.exe
12/03/2004  15:49               623 test.vb

poi eseguito:

dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne

Si potrebbe voler scrivere

dim p as personne=l("nom")

dove l sarebbe di tipo [listOfPeople]. In questo caso, vogliamo indicizzare la lista l non in base al numero dell'elemento, ma al nome di una persona. Per farlo, definiamo una nuova proprietà indicizzata predefinita:


    ' another indexer
    Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
        Get
            ' we search for the person with name N
            Dim i As Integer
            For i = 0 To Count - 1
                If CType(Me(i), personne).nom = N Then
                    Return i
                End If
            Next i
            Return -1
        End Get
    End Property

La prima riga


    Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer

indica che stiamo creando una nuova proprietà indicizzata predefinita. Tutte le proprietà predefinite devono avere lo stesso nome, in questo caso [Index]. La nuova proprietà [Index] indicizza la classe PeopleList utilizzando una stringa N. Il risultato di PeopleList(N) è un numero intero. Questo numero intero sarà la posizione nell'elenco della persona con il nome N, oppure -1 se quella persona non è presente nell'elenco. Definiamo solo la proprietà get, impedendo così l'assegnazione listOfPeople("name")=value, che avrebbe richiesto la definizione della proprietà set. Da qui la parola chiave [ReadOnly]. La parola chiave [Shadows] è necessaria per nascondere la proprietà predefinita della classe base (anche se non ha la stessa firma).

Nel corpo del metodo get, percorriamo l'elenco delle persone alla ricerca del nome N passato come parametro. Se lo troviamo nella posizione i, restituiamo i; altrimenti, restituiamo -1.

Un nuovo programma di test potrebbe essere il seguente:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System

' test pg
Module test
    Sub Main()
        ' a list of people
        Dim l As New listeDePersonnes
        ' add people
        l.Add(New personne("jean", "dumornet", 10))
        l.Add(New personne("pauline", "duchemin", 12))
        ' display
        Console.Out.WriteLine(("l=" + l.ToString))
        l.Add(New personne("jacques", "tartifume", 27))
        Console.Out.WriteLine(("l=" + l.ToString))
        ' change item 1
        l(1) = New personne("sylvie", "cachan", 5)
        ' display element 1
        Console.Out.WriteLine(("l[1]=" + l(1).ToString))
        ' display list l
        Console.Out.WriteLine(("l=" + l.ToString))
        ' people search
        Dim noms() As String = New [String]() {"cachan", "inconnu"}
        Dim i As Integer
        For i = 0 To noms.Length - 1
            Dim inom As Integer = l(noms(i))
            If inom <> -1 Then
                Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
            Else
                Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
            End If
        Next i
    End Sub    
End Module

L'esecuzione produce i seguenti risultati:

personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas

3.4. Strutture

La struttura VB.NET deriva direttamente dalla struttura del linguaggio C ed è molto simile a una classe. Una struttura è definita come segue:


Structure spersonne
' attributs
    ...
' propriétés
...
' constructeurs
...
' méthodes
End Structure

Nonostante le somiglianze nella sintassi, esistono differenze significative tra classi e strutture. Ad esempio, il concetto di ereditarietà non esiste nelle strutture. Se stiamo scrivendo una classe che non è destinata a derivare da un'altra, quali sono le differenze tra strutture e classi che ci aiuteranno a scegliere tra le due? Usiamo il seguente esempio per scoprirlo:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' spersonne structure
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure
 
' class cperson
Class cpersonne
    Public nom As String
    Public age As Integer
End Class
 
' a test module
Public Module test
    Sub Main()
        ' a person p1
        Dim sp1 As spersonne
        sp1.nom = "paul"
        sp1.age = 10
        Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
        ' a p2 person
        Dim sp2 As spersonne = sp1
        Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
        ' sp2 is modified
        sp2.nom = "nicole"
        sp2.age = 30
        ' checking sp1 and sp2
        Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
        Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
 
        ' a cperson cp1
        Dim cp1 As New cpersonne
        cp1.nom = "paul"
        cp1.age = 10
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        ' a P2 person
        Dim cp2 As cpersonne = cp1
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
        ' P2 is modified
        cp2.nom = "nicole"
        cp2.age = 30
        ' p1 and P2 verification
        Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
        Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
    End Sub
End Module

Se eseguiamo questo programma, otteniamo i seguenti risultati:

sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)

Mentre nelle pagine precedenti di questo capitolo abbiamo utilizzato una classe Person, ora utilizziamo una PersonStructure:


' structure spersonne
Structure spersonne
    Public nom As String
    Public age As Integer
End Structure

La dichiarazione


        Dim sp1 As spersonne

crea una struttura (nome, età) e il valore di sp1 è questa struttura stessa.

La dichiarazione


        Dim cp1 As New cpersonne

crea un oggetto [cpersonne] (più o meno equivalente alla nostra struttura), e cp1 è quindi l'indirizzo (il riferimento) di quell'oggetto.

Riassumendo

  • nel caso della struttura, il valore di sp1 è la struttura stessa
  • nel caso di una classe, il valore di p1 è l'indirizzo dell'oggetto creato

Quando nel programma scriviamo


        Dim sp2 As spersonne = sp1

viene creata una nuova struttura (nome, età) e inizializzata con il valore di p1, ovvero la struttura stessa.

La struttura di sp1 viene quindi duplicata in sp2. Si tratta di una copia per valore.

L'istruzione


        Dim cp2 As cpersonne = cp1

funziona in modo diverso. Il valore di cp1 viene copiato in cp2, ma poiché questo valore è in realtà l'indirizzo dell'oggetto, l'oggetto stesso non viene duplicato. Ha semplicemente due riferimenti che puntano ad esso:

Nel caso della struttura, se modifichiamo il valore di sp2, non modifichiamo il valore di sp1, come dimostra il programma. Nel caso dell'oggetto, se modifichiamo l'oggetto puntato da cp2, viene modificato anche quello puntato da cp1 poiché sono lo stesso. Anche questo è dimostrato dai risultati del programma.

Da queste spiegazioni possiamo quindi concludere che:

  • il valore di una variabile di tipo struttura è la struttura stessa
  • il valore di una variabile di tipo oggetto è l'indirizzo dell'oggetto a cui punta

Una volta compresa questa differenza fondamentale, la struttura si rivela molto simile a una classe, come mostra il seguente nuovo esempio:


' options
Option Strict On
Option Explicit On 

' namespaces
Imports System
 
' structure person
Structure personne
    ' attributes
    Private _nom As String
    Private _age As Integer
 
    ' properties
    Public Property nom() As String
        Get
            Return _nom
        End Get
        Set(ByVal Value As String)
            _nom = value
        End Set
    End Property
 
    Public Property age() As Integer
        Get
            Return _age
        End Get
        Set(ByVal Value As Integer)
            _age = value
        End Set
    End Property
 
    ' Manufacturer
    Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
        _nom = NOM
        _age = AGE
    End Sub    'New
 
    ' TOSTRING
    Public Overrides Function ToString() As String
        Return "personne(" & nom & "," & age & ")"
    End Function
End Structure
 
' a test module
Module test
    Sub Main()
        ' one person p1
        Dim p1 As New personne("paul", 10)
        Console.Out.WriteLine(("p1=" & p1.ToString))
        ' one person p2
        Dim p2 As personne = p1
        Console.Out.WriteLine(("p2=" & p2.ToString))
        ' p2 is modified
        p2.nom = "nicole"
        p2.age = 30
        ' checking p1 and p2
        Console.Out.WriteLine(("p1=" & p1.ToString))
        Console.Out.WriteLine(("p2=" & p2.ToString))
    End Sub
End Module

Si ottengono i seguenti risultati di esecuzione:

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

L'unica differenza degna di nota qui tra una struttura e una classe è che con una classe, gli oggetti p1 e p2 avrebbero avuto lo stesso valore alla fine del programma, ovvero quello di p2.

3.5. Interfacce

Un'interfaccia è un insieme di prototipi di metodi o proprietà che costituisce un contratto. Una classe che decide di implementare un'interfaccia si impegna a fornire un'implementazione di tutti i metodi definiti nell'interfaccia. Il compilatore verifica questa implementazione. Ecco, ad esempio, la definizione dell'interfaccia Istats:

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

Qualsiasi classe che implementi questa interfaccia verrà dichiarata come

public class C 
 Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class

I metodi [mean] e [standardDev] devono essere definiti nella classe C. Si consideri il seguente codice:


' options
Option Strict On
Option Explicit On 

' namespaces
Imports System
 
' structure
Public Structure élève
    Public _nom As String
    Public _note As Double
 
    ' manufacturer
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub
End Structure
 
' class notes
Public Class notes
    ' attribute
    Protected _matière As String
    Protected _élèves() As élève
 
    ' manufacturer
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        ' student & subject memorization
        Me._matière = MATIERE
        Me._élèves = ELEVES
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Dim valeur As String = "matière=" + _matière + ", notes=("
        Dim i As Integer
        ' concatenate all the notes
        For i = 0 To (_élèves.Length - 1) - 1
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
        Next i
        'final note
        If _élèves.Length <> 0 Then
            valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
        End If
        valeur += ")"
        ' end
        Return valeur
    End Function
End Class

La classe notes raccoglie i voti di una classe in una materia:


Public Class notes
    ' attribut
    Protected _matière As String
    Protected _élèves() As élève

Gli attributi sono dichiarati come protetti in modo da poter essere accessibili da una classe derivata. Il tipo student è una struttura che memorizza il nome dello studente e il suo voto nella materia:


Public Structure élève
    Public _nom As String
    Public _note As Double
 
    ' manufacturer
    Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
        Me._nom = NOM
        Me._note = NOTE
    End Sub    
End Structure

Decidiamo di derivare questa classe notes in una classe notesStats che avrà due attributi aggiuntivi: la media e la deviazione standard dei voti:


Public Class notesStats
    Inherits notes
    Implements Istats
 
    ' attributes
    Private _moyenne As Double
    Private _écartType As Double

La classe notesStats implementa la seguente interfaccia Istats:

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

Ciò significa che la classe notesStats deve avere due metodi denominati average e standardDeviation con le firme specificate nell'interfaccia Istats. La classe notesStats è la seguente:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class notesStats
    Inherits notes
    Implements Istats
 
    ' attributes
    Private _moyenne As Double
    Private _écartType As Double
 
    ' manufacturer
    Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
        MyBase.New(MATIERE, ELEVES)
        ' average score calculation
        Dim somme As Double = 0
        Dim i As Integer
        For i = 0 To ELEVES.Length - 1
            somme += ELEVES(i)._note
        Next i
        If ELEVES.Length <> 0 Then
            _moyenne = somme / ELEVES.Length
        Else
            _moyenne = -1
        End If
        ' standard deviation
        Dim carrés As Double = 0
        For i = 0 To ELEVES.Length - 1
            carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
        Next i
        If ELEVES.Length <> 0 Then
            _écartType = Math.Sqrt((carrés / ELEVES.Length))
        Else
            _écartType = -1
        End If
    End Sub
 
    ' ToString
    Public Overrides Function ToString() As String
        Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
    End Function    'ToString
 
    ' istats interface methods
    Public Function moyenne() As Double Implements Istats.moyenne
        ' makes the average score
        Return _moyenne
    End Function
 
    Public Function écartType() As Double Implements Istats.écartType
        ' makes the standard deviation
        Return _écartType
    End Function
End Class

La media _mean e la deviazione standard _standardDev vengono calcolate al momento della creazione dell'oggetto. Pertanto, i metodi mean e standardDev restituiscono semplicemente i valori degli attributi _mean e _standardDev. Entrambi i metodi restituiscono -1 se l'array degli studenti è vuoto.

La seguente classe di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Module test
    Sub Main()
        ' some students & notes
        Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
        ' recorded in a notes object
        Dim anglais As New notes("anglais", ELEVES)
        ' and display
        Console.Out.WriteLine((anglais.ToString))
        ' idem with mean and standard deviation
        anglais = New notesStats("anglais", ELEVES)
        Console.Out.WriteLine((anglais.ToString))
    End Sub
End Module

fornisce i seguenti risultati:

matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545

La classe notesStats avrebbe potuto benissimo implementare i metodi average e standardDev da sola senza specificare che implementava l'interfaccia Istats. Qual è lo scopo delle interfacce? È questo: una funzione può accettare come parametro formale un valore di tipo I. Qualsiasi oggetto della classe C che implementi l'interfaccia I può quindi essere un parametro effettivo di quella funzione. Consideriamo il seguente esempio:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' an Iexample interface
Public Interface Iexemple
    Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
    Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
 
' a 1st class
Public Class classe1
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 10
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 20
    End Function
End Class
 
'a 2nd class
Public Class classe2
    Implements Iexemple
 
    Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
        Return a + b + 100
    End Function
 
    Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
        Return a - b + 200
    End Function
End Class

L'interfaccia Iexample definisce due metodi: add e subtract. Le classi Class1 e Class2 implementano questa interfaccia. Si noti che queste classi non fanno nient'altro, al fine di semplificare l'esempio. Consideriamo ora il seguente esempio:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' test class
Module test
 
    'calculate
    Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
        Console.Out.WriteLine(inter.ajouter(i, j))
        Console.Out.WriteLine(inter.soustraire(i, j))
    End Sub
 
    ' the Hand function
    Sub Main()
        ' creation of two objects class1 and class2
        Dim c1 As New classe1
        Dim c2 As New classe2
        ' static function calls calculate
        calculer(4, 3, c1)
        calculer(14, 13, c2)
    End Sub
End Module

La funzione calculate accetta come parametro un elemento di tipo Iexample. Può quindi ricevere come parametro un oggetto di tipo class1 o class2. Questo è ciò che viene fatto nella procedura Main con i seguenti risultati:

17
21
127
201

Possiamo quindi notare che questa proprietà è simile al polimorfismo di cui abbiamo parlato per le classi. Se un insieme di classi Ci, che non sono correlate tra loro tramite ereditarietà (e che quindi non possono avvalersi del polimorfismo basato sull'ereditarietà), condividono un insieme di metodi con la stessa firma, può essere utile raggruppare tali metodi in un'interfaccia I da cui tutte le classi interessate erediterebbero. Le istanze di queste classi Ci possono quindi essere utilizzate come parametri per funzioni che accettano un parametro di tipo I, ovvero funzioni che utilizzano solo i metodi degli oggetti Ci definiti nell'interfaccia I e non gli attributi e i metodi specifici delle varie classi Ci. Infine, si noti che l'ereditarietà dell'interfaccia può essere multipla, ovvero possiamo scrivere


Public Class classe
    Implements I1,I2,...

dove le Ij sono interfacce.

3.6. Spazi dei nomi

Per scrivere una riga sullo schermo, usiamo l'istruzione

Console.Out.WriteLine(...)

Se osserviamo la definizione della classe Console


Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)

vediamo che fa parte dello spazio dei nomi System. Ciò significa che la classe Console dovrebbe essere indicata come System.Console, e dovremmo quindi scrivere:

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

Possiamo evitare questo problema utilizzando una clausola imports:

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

Si dice che si importa lo spazio dei nomi System utilizzando la clausola imports. Quando il compilatore incontra un nome di classe (in questo caso, Console), lo cercherà nei vari spazi dei nomi importati dalle clausole imports. In questo caso, troverà la classe Console nello spazio dei nomi System. Ora notiamo la seconda informazione associata alla classe Console:


Assembly: Mscorlib (in Mscorlib.dll)

Questa riga indica in quale "assembly" si trova la definizione della classe Console. Quando si compila al di fuori di Visual Studio.NET ed è necessario specificare i riferimenti alle varie DLL contenenti le classi che si devono utilizzare, questa informazione può essere utile. Ricordate che per fare riferimento alle DLL necessarie per compilare una classe, si scrive:

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

Quando si crea una classe, è possibile crearla all'interno di uno spazio dei nomi. Lo scopo di questi spazi dei nomi è quello di evitare conflitti di nomi tra le classi, ad esempio quando vengono vendute. Si considerino due aziende, E1 ed E2, che distribuiscono classi contenute rispettivamente nelle DLL E1.dll ed E2.dll. Supponiamo che un cliente C acquisti questi due set di classi, in cui entrambe le aziende hanno definito una classe Person. Il cliente C compila un programma come segue:

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

Se il file sorgente prog.vb utilizza la classe Person, il compilatore non saprà se utilizzare la classe Person contenuta in E1.dll o quella contenuta in E2.dll. Verrà segnalato un errore. Se l'azienda E1 si preoccupa di creare le proprie classi in uno spazio dei nomi chiamato E1 e l'azienda E2 in uno spazio dei nomi chiamato E2, le due classi Person saranno denominate E1.Person ed E2.Person. Il client deve utilizzare E1.Person o E2.Person nelle proprie classi, ma non Person. Lo spazio dei nomi risolve questa ambiguità. Per creare una classe in uno spazio dei nomi, si scrive:


Namespace istia.st
    Public Class personne
        ' définition de la classe
...
  end Class
end Namespace

Per questo esempio, creiamo la classe Person che abbiamo studiato in precedenza in uno spazio dei nomi. Sceglieremo istia.st come spazio dei nomi. La classe Person diventa:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
' creation of istia.st namespace
Namespace istia.st
    Public Class personne
        ' attributes
        Private prenom As String
        Private nom As String
        Private age As Integer
 
        ' method
        Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
            Me.prenom = P
            Me.nom = N
            Me.age = age
        End Sub
 
        ' method
        Public Sub identifie()
            Console.Out.WriteLine((prenom & "," & nom & "," & age))
        End Sub
    End Class
End Namespace

Questa classe viene compilata in person.dll:

dos>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 Person in una classe di test:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports istia.st
 
' pg test
Public Module test
    Sub Main()
        Dim p1 As New personne
        p1.initialise("Jean", "Dupont", 30)
        p1.identifie()
    End Sub
End Module

Per evitare di scrivere


        Dim p1 As New istia.st.personne

Abbiamo importato lo spazio dei nomi istia.st con una clausola Imports:


Imports istia.st

Ora compiliamo il programma di prova:

dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:05               254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004  18:06             3 584 personne.dll
11/03/2004  18:27               610 personne.vb
12/03/2004  18:08             3 072 test.exe
12/03/2004  18:05               254 test.vb

Questo genera un file test.exe che, una volta eseguito, produce i seguenti risultati:

Jean,Dupont,30

3.7. Esempio di calcolo delle imposte

Riprendiamo il calcolo delle imposte già trattato nel capitolo precedente e lo elaboriamo utilizzando una classe. Rivediamo il problema:

Consideriamo il caso semplificato di un contribuente che deve dichiarare solo il proprio stipendio:

  • calcoliamo il numero di scaglioni fiscali per il dipendente come nbParts = nbEnfants / 2 + 1 se è celibe, e nbEnfants / 2 + 2 se è coniugato, dove nbEnfants è il numero di figli.
  • Se ha almeno tre figli, riceve una quota aggiuntiva pari a metà
  • Calcoliamo il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
  • Calcoliamo il suo coefficiente familiare QF = R / nbParts
  • calcoliamo la sua imposta I. Consideriamo la seguente tabella:
12620,0
0
0
13.190
0,05
631
15.640
0,1
1.290,5
24.740
0,15
2.072,5
31.810
0,2
3.309,5
39.970
0,25
4.900
48.360
0,3
6.898,5
55.790
0,35
9.316,5
92.970
0,4
12.106
127.860
0,45
16.754,5
151.250
0,50
23.147,5
172.040
0,55
30.710
195.000
0,60
39.312
0
0,65
49.062

Ogni riga ha 3 campi. Per calcolare l'imposta I, trova la prima riga in cui QF <= campo1. Ad esempio, se QF = 23.000, la riga trovata sarà

    24740        0.15        2072.5

L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la condizione QF<=field1 non è mai soddisfatta, vengono utilizzati i coefficienti dell'ultima riga. Qui:

    0                0.65        49062

il che dà l'imposta I = 0,65 * R - 49062 * nbParts.

La classe **impot** sarà definita come segue:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
 
Public Class impot
    ' data required for tax calculation
    ' come from an external source
    Private limites(), coeffR(), coeffN() As Decimal
 
    ' manufacturer
    Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
        ' check that the 3 arrays have the same size
        Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
        If Not OK Then
            Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
        End If
        ' it's good
        Me.limites = LIMITES
        Me.coeffR = COEFFR
        Me.coeffN = COEFFN
    End Sub
 
    ' tAX CALCULATION
    Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
        ' calculating the number of shares
        Dim nbParts As Decimal
        If marié Then
            nbParts = CDec(nbEnfants) / 2 + 2
        Else
            nbParts = CDec(nbEnfants) / 2 + 1
        End If
        If nbEnfants >= 3 Then
            nbParts += 0.5D
        End If
        ' calculation of taxable income & family quota
        Dim revenu As Decimal = 0.72D * salaire
        Dim QF As Decimal = revenu / nbParts
        ' tAX CALCULATION
        limites((limites.Length - 1)) = QF + 1
        Dim i As Integer = 0
        While QF > limites(i)
            i += 1
        End While
        Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
    End Function
End Class

Viene creato un oggetto fiscale con i dati necessari per calcolare l'imposta di un contribuente. Questa è la parte stabile dell'oggetto. Una volta creato l'oggetto, il suo metodo **calculate** può essere chiamato ripetutamente per calcolare l'imposta del contribuente in base al suo stato civile (coniugato o meno), al numero di figli e allo stipendio annuale. Un programma di prova potrebbe apparire così:


' options
Option Strict On
Option Explicit On 
 
' namespaces
Imports System
Imports Microsoft.VisualBasic
 
Module test
    Sub Main()
        ' interactive tax calculator
        ' the user enters three data points on the keyboard: married nbEnfants salary
        ' the program then displays the tax payable
        Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
 
        ' data tables required for tax calculation
        Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
        Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
        Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
 
        ' tax object creation
        Dim objImpôt As impot = Nothing
        Try
            objImpôt = New impot(limites, coeffR, coeffN)
        Catch ex As Exception
            Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
            Environment.Exit(1)
        End Try
        ' infinite loop
        Dim marié As String
        Dim nbEnfants As Integer
        Dim salaire As Long
        While True
            ' tax calculation parameters are requested
            Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
            Dim paramètres As String = Console.In.ReadLine().Trim()
            ' anything to do?
            If paramètres Is Nothing OrElse paramètres = "" Then
                Exit While
            End If
            ' check the number of arguments in the input line
            Dim erreur As Boolean = False
            Dim args As String() = paramètres.Split(Nothing)
            Dim nbParamètres As Integer = args.Length
            If nbParamètres <> 3 Then
                Console.Error.WriteLine(syntaxe)
                erreur = True
            End If
            ' checking parameter validity
            If Not erreur Then
                ' married
                marié = args(0).ToLower()
                If marié <> "o" And marié <> "n" Then
                    erreur = True
                End If
                ' nbEnfants
                Try
                    nbEnfants = Integer.Parse(args(1))
                    If nbEnfants < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
                ' salary
                Try
                    salaire = Integer.Parse(args(2))
                    If salaire < 0 Then
                        Throw New Exception
                    End If
                Catch
                    erreur = True
                End Try
            End If
            ' if the parameters are correct - the tax is calculated
            If Not erreur Then
                Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
            Else
                Console.Error.WriteLine(syntaxe)
            End If
        End While
    End Sub
End Module

Ecco un esempio del programma precedente in azione:

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 :