3. Classes, stuctures, interfaces
3.1. L' objet par l'exemple
3.1.1. Généralités
Nous abordons maintenant, par l'exemple, la programmation objet. Un objet est une entité qui contient des données qui définissent son état (on les appelle des propriétés) et des fonctions (on les appelle des méthodes). Un objet est créé selon un modèle qu'on appelle une classe :
Public Class c1
' attributs
Private p1 As type1
Private p2 As type2
....
' méthode
Public Sub m1(....)
...
End Sub
' méthode
Public Function m2(...)
....
End Function
End Class
A partir de la classe C1 précédente, on peut créer de nombreux objets O1, O2,… Tous auront les propriétés p1, p2,… et les méthodes m3, m4, … Mais ils auront des valeurs différentes pour leurs propriétés pi ayant ainsi chacun un état qui leur est propre. Par analogie la déclaration
crée deux objets (le terme est incorrect ici) de type (classe) Integer. Leur seule propriété est leur valeur. Si O1 est un objet de type C1, O1.p1 désigne la propriété p1 de O1 et O1.m1 la méthode m1 de O1. Considérons un premier modèle d'objet : la classe personne.
3.1.2. Définition de la classe personne
La définition de la classe personne sera la suivante :
Public Class personne
' attributs
Private prenom As String
Private nom As String
Private age As Integer
' méthode
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
' méthode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Nous avons ici la définition d'une classe, donc d'un type de données. Lorsqu'on va créer des variables de ce type, on les appellera des objets ou des instances de classes. Une classe est donc un moule à partir duquel sont construits des objets. Les membres ou champs d'une classe peuvent être des données (attributs), des méthodes (fonctions), des propriétés. Les propriétés sont des méthodes particulières servant à connaître ou fixer la valeur d'attributs de l'objet. Ces champs peuvent être accompagnés de l'un des trois mots clés suivants :
Un champ privé (private) n'est accessible que par les seules méthodes internes de la classe | |
Un champ public (public) est accessible par toute fonction définie ou non au sein de la classe | |
Un champ protégé (protected) n'est accessible que par les seules méthodes internes de la classe ou d'un objet dérivé (voir ultérieurement le concept d'héritage). |
En général, les données d'une classe sont déclarées privées alors que ses méthodes et propriétés sont déclarées publiques. Cela signifie que l'utilisateur d'un objet (le programmeur) :
- n'aura pas accès directement aux données privées de l'objet
- pourra faire appel aux méthodes publiques de l'objet et notamment à celles qui donneront accès à ses données privées.
La syntaxe de déclaration d'une clase est la suivante :
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'ordre de déclaration des attributs private, protected et public est quelconque.
3.1.3. La méthode initialise
Revenons à notre classe [personne] déclarée comme :
Public Class personne
' attributs
Private prenom As String
Private nom As String
Private age As Integer
' méthode
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
' méthode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Quel est le rôle de la méthode initialise ? Parce que nom, prenom et age sont des données privées de la classe personne, les instructions :
sont illégales. Il nous faut initialiser un objet de type personne via une méthode publique. C'est le rôle de la méthode initialise. On écrira :
L'écriture p1.initialise est légale car initialise est d'accès public.
3.1.4. L'opérateur new
La séquence d'instructions
est incorrecte. L'instruction
déclare p1 comme une référence à un objet de type personne. Cet objet n'existe pas encore et donc p1 n'est pas initialisé. C'est comme si on écrivait :
où on indique explicitement avec le mot clé nothing que la variable p1 ne référence encore aucun objet. Lorsqu'on écrit ensuite
on fait appel à la méthode initialise de l'objet référencé par p1. Or cet objet n'existe pas encore et le compilateur signalera l'erreur. Pour que p1 référence un objet, il faut écrire :
Cela a pour effet de créer un objet de type personne non encore initialisé : les attributs nom et prenom qui sont des références d'objets de type String auront la valeur nothing, et age la valeur 0. Il y a donc une initialisation par défaut. Maintenant que p1 référence un objet, l'instruction d'initialisation de cet objet
est valide.
3.1.5. Le mot clé Me
Regardons le code de la méthode initialise :
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
L'instruction Me.prenom=P signifie que l'attribut prenom de l'objet courant (Me) reçoit la valeur P. Le mot clé Me désigne l'objet courant : celui dans lequel se trouve la méthode exécutée. Comment le connaît-on ? Regardons comment se fait l'initialisation de l'objet référencé par p1 dans le programme appelant :
C'est la méthode initialise de l'objet p1 qui est appelée. Lorsque dans cette méthode, on référence l'objet Me, on référence en fait l'objet p1. La méthode initialise aurait aussi pu être écrite comme suit :
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Lorsqu'une méthode d'un objet référence un attribut A de cet objet, l'écriture Me.A est implicite. On doit l'utiliser explicitement lorsqu'il y a conflit d'identificateurs. C'est le cas de l'instruction :
Me.age=age;
où age désigne un attribut de l'objet courant ainsi que le paramètre age reçu par la méthode. Il faut alors lever l'ambiguïté en désignant l'attribut age par Me.age.
3.1.6. Un programme de test
Voici un court programme de test :
Public Class personne
' attributs
Private prenom As String
Private nom As String
Private age As Integer
' méthode
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
' méthode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
et les résultats obtenus :
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Utiliser un fichier de classes compilées (assembly)
On notera que dans l'exemple précédent il y a deux classes dans notre programme de test : les classes personne et test1. Il y a une autre façon de procéder :
-
on compile la classe personne dans un fichier particulier appelé un assemblage (assembly). Ce fichier a une extension .dll
-
on compile la classe test1 en référençant l'assemblage qui contient la classe personne.
Les deux fichiers source deviennent les suivants :
test.vb | |
personne2.vb | |
La classe personne est compilée par l'instruction suivante :
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 compilation a produit un fichier personne2.dll. C'est l'option de compilation /t:library qui indique de produire un fichier "assembly". Maintenant compilons le fichier 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'option de compilation /r:personne2.dll indique au compilateur qu'il trouvera certaines classes dans le fichier personne2.dll. Lorsque dans le fichier source test.vb, il trouvera une référence à la classe personne classe non déclarée dans le source test.vb, il cherchera la classe personne dans les fichiers .dll référencés par l'option /r. Il trouvera ici la classe personne dans l'assemblage personne2.dll. On aurait pu mettre dans cet assemblage d'autres classes. Pour utiliser lors de la compilation plusieurs fichiers de classes compilées, on écrira :
L'exécution du programme test1.exe donne les résultats suivants :
3.1.8. Une autre méthode initialise
Considérons toujours la classe personne et rajoutons-lui la méthode suivante :
' méthode
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
On a maintenant deux méthodes portant le nom initialise : c'est légal tant qu'elles admettent des paramètres différents. C'est le cas ici. Le paramètre est maintenant une référence P à une personne. Les attributs de la personne P sont alors affectés à l'objet courant (Me). On remarquera que la méthode initialise a un accès direct aux attributs de l'objet P bien que ceux-ci soient de type private. C'est toujours vrai : un objet O1 d'une classe C a toujours accès aux attributs des objets de la même classe C. Voici un test de la nouvelle classe personne, celle-ci ayant été compilée dans personne.dll comme il a été expliqué précédemment :
' options
Option Explicit On
Option Strict On
' espaces de noms
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
et ses résultats :
3.1.9. Constructeurs de la classe personne
Un constructeur est une procédure qui porte le nom New et qui est appelée lors de la création de l'objet. On s'en sert généralement pour l'initialiser. Si une classe a un constructeur acceptant n arguments argi, la déclaration et l'initialisation d'un objet de cette classe pourra se faire sous la forme :
dim objet as classe =new classe(arg1,arg2, ... argn)
ou
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Lorsqu'une classe a un ou plusieurs constructeurs, l'un de ces constructeurs doit être obligatoirement utilisé pour créer un objet de cette classe. Si une classe C n'a aucun constructeur, elle en a un par défaut qui est le constructeur sans paramètres : public New(). Les attributs de l'objet sont alors initialisés avec des valeurs par défaut. C'est ce qui s'est passé dans les programmes précédents, où on avait écrit :
Créons deux constructeurs à notre classe personne :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' la classe personne
Public Class personne
' attributs
Private prenom As String
Private nom As String
Private age As Integer
' constructeurs
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
' méthodes d'initialisation de l'objet
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
' méthode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Nos deux constructeurs se contentent de faire appel aux méthodes initialise correspondantes. On rappelle que lorsque dans un constructeur, on trouve la notation initialise(P) par exemple, le compilateur traduit par Me.initialise(P). Dans le constructeur, la méthode initialise est donc appelée pour travailler sur l'objet référencé par Me, c'est à dire l'objet courant, celui qui est en cours de construction. Voici un court programme de test :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
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
et les résultats obtenus :
3.1.10. Les références d'objets
Nous utilisons toujours la même classe personne. Le programme de test devient le suivant :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 référence le même objet que p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 référence un objet qui sera une copie de l'objet référencé par p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' on change l'état de l'objet référencé par p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' comme p2=p1, l'objet référencé par p2 a du changer d'état
Console.Out.Write("p2=")
p2.identifie()
' comme p3 ne référence pas le même objet que p1, l'objet référencé par p3 n'a pas du changer
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Les résultats obtenus sont les suivants :
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
Lorsqu'on déclare la variable p1 par
p1 référence l'objet personne("Jean","Dupont",30) mais n'est pas l'objet lui-même. En C, on dirait que c'est un pointeur, c.a.d. l'adresse de l'objet créé. Si on écrit ensuite :
Ce n'est pas l'objet personne("Jean","Dupont",30) qui est modifié, c'est la référence p1 qui change de valeur. L'objet personne("Jean","Dupont",30) sera "perdu" s'il n'est référencé par aucune autre variable.
Lorsqu'on écrit :
on initialise le pointeur p2 : il "pointe" sur le même objet (il désigne le même objet) que le pointeur p1. Ainsi si on modifie l'objet "pointé" (ou référencé) par p1, on modifie celui référencé par p2.
Lorsqu'on écrit :
il y a création d'un nouvel objet, copie de l'objet référencé par p1. Ce nouvel objet sera référencé par p3. Si on modifie l'objet "pointé" (ou référencé) par p1, on ne modifie en rien celui référencé par p3. C'est ce que montrent les résultats obtenus.
3.1.11. Les objets temporaires
Dans une expression, on peut faire appel explicitement au constructeur d'un objet : celui-ci est construit, mais nous n'y avons pas accès (pour le modifier par exemple). Cet objet temporaire est construit pour les besoins d'évaluation de l'expression puis abandonné. L'espace mémoire qu'il occupait sera automatiquement récupéré ultérieurement par un programme appelé "ramasse-miettes" dont le rôle est de récupérer l'espace mémoire occupé par des objets qui ne sont plus référencés par des données du programme. Considérons le nouveau programme de test suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' pg de test
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
et modifions les constructeurs de la classe personne afin qu'ils affichent un message :
' constructeurs
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
Nous obtenons les résultats suivants :
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
montrant la construction successive des deux objets temporaires.
3.1.12. Méthodes de lecture et d'écriture des attributs privés
Nous rajoutons à la classe personne les méthodes nécessaires pour lire ou modifier l'état des attributs des objets :
Imports System
Public Class personne
' attributs
Private prenom As [String]
Private nom As [String]
Private age As Integer
' constructeurs
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
' identifie
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accesseurs
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
'modifieurs
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
Nous testons la nouvelle classe avec le programme suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' pg de test
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
et nous obtenons les résultats suivants :
3.1.13. Les propriétés
Il existe une autre façon d'avoir accès aux attributs d'une classe c'est de créer des propriétés. Celles-ci nous permettent de manipuler des attributs privés comme s'ils étaient publics. Considérons la classe personne suivante où les accesseurs et modifieurs précédents ont été remplacés par des propriétés en lecture et écriture :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' classe personne
Public Class personne
' attributs
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructeurs
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
' identifie
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriétés
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)
' age valide ?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Une propriété Property permet de lire (get) ou de fixer (set) la valeur d'un attribut. Dans notre exemple, nous avons préfixé les noms des attributs du signe _ afin que les propriétés portent le nom des attributs primitifs. En effet, une propriété ne peut porter le même nom que l'attribut qu'elle gère car alors il y a un conflit de noms dans la classe. Nous avons donc appelé nos attributs _prenom, _nom, _age et modifié les constructeurs et méthodes en conséquence. Nous avons ensuite créé trois propriétés nom, prenom et age. Une propriété est déclarée comme suit :
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
où Type doit être le type de l'attribut géré par la propriété. Elle peut avoir deux méthodes appelées get et set. La méthode get est habituellement chargée de rendre la valeur de l'attribut qu'elle gère (elle pourrait rendre autre chose, rien ne l'empêche). La méthode set reçoit un paramètre appelé value qu'elle affecte normalement à l'attribut qu'elle gère. Elle peut en profiter pour faire des vérifications sur la validité de la valeur reçue et éventuellement lancer un exception si la valeur se révèle invalide. C'est ce qui est fait ici pour l'âge.
Comment ces méthodes get et set sont-elles appelées ? Considérons le programme de test suivant :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
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
Dans l'instruction
on cherche à avoir les valeurs des propriétés prenom, nom et age de la personne P. C'est la méthode get de ces propriétés qui est alors appelée et qui rend la valeur de l'attribut qu'elles gèrent.
Dans l'instruction
on veut fixer la valeur de la propriété age. C'est alors la méthode set de cette propriété qui est alors appelée. Elle recevra 56 dans son paramètre value.
Une propriété P d'une classe C qui ne définirait que la méthode get est dite en lecture seule. Si c est un objet de classe C, l'opération c.P=valeur sera alors refusée par le compilateur.
L'exécution du programme de test précédent donne les résultats suivants :
Les propriétés nous permettent donc de manipuler des attributs privés comme s'ils étaient publics.
3.1.14. Les méthodes et attributs de classe
Supposons qu'on veuille compter le nombre d'objets [personne] créées dans une application. On peut soi-même gérer un compteur mais on risque d'oublier les objets temporaires qui sont créés ici ou là. Il semblerait plus sûr d'inclure dans les constructeurs de la classe [personne], une instruction incrémentant un compteur. Le problème est de passer une référence de ce compteur afin que le constructeur puisse l'incrémenter : il faut leur passer un nouveau paramètre. On peut aussi inclure le compteur dans la définition de la classe. Comme c'est un attribut de la classe elle-même et non d'un objet particulier de cette classe, on le déclare différemment avec le mot clé Shared :
Pour le référencer, on écrit personne._nbPersonnes pour montrer que c'est un attribut de la classe personne elle-même. Ici, nous avons créé un attribut privé auquel on n'aura pas accès directement en-dehors de la classe. On crée donc une propriété publique pour donner accès à l'attribut de classe nbPersonnes. Pour rendre la valeur de nbPersonnes la méthode get de cette propriété n'a pas besoin d'un objet personne particulier : en effet _nbPersonnes n'est pas l'attribut d'un objet particulier, il est l'attribut de toute une classe. Aussi a-t-on besoin d'une propriété déclarée elle-aussi Shared :
qui de l'extérieur sera appelée avec la syntaxe personne.nbPersonnes. La propriété est déclarée en lecture seule (ReadOnly) car elle ne propose pas de méthode set. Voici un exemple. La classe personne devient la suivante :
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' classe
Public Class personne
' attributs de classe
Private Shared _nbPersonnes As Long = 0
' attributs d'instance
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructeurs
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' une personne de plus
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' une personne de plus
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifie
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propriété de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriétés d'instance
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)
' age valide ?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Avec le programme suivant :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
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
on obtient les résultats suivants :
3.1.15. Passage d'un objet à une fonction
Nous avons déjà dit que par défaut VB.NET passait les paramètres effectifs d'une fonction par valeur : les valeurs des paramètres effectifs sont recopiées dans les paramètres formels. Dans le cas d'un objet, il ne faut pas se laisser tromper par l'abus de langage qui est fait systématiquement en parlant d'objet au lieu de référence d'objet. Un objet n'est manipulé que via une référence (un pointeur) sur lui. Ce qui est donc transmis à une fonction, n'est pas l'objet lui-même mais une référence sur cet objet. C'est donc la valeur de la référence et non la valeur de l'objet lui-même qui est dupliquée dans le paramètre formel : il n'y a pas construction d'un nouvel objet. Si une référence d'objet R1 est transmise à une fonction, elle sera recopiée dans le paramètre formel correspondant R2. Aussi les références R2 et R1 désignent-elles le même objet. Si la fonction modifie l'objet pointé par R2, elle modifie évidemment celui référencé par R1 puisque c'est le même.
![]() |
C'est ce que montre l'exemple suivant :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
Module test
Sub Main()
' une personne p1
Dim p1 As New personne("Jean", "Dupont", 30)
' affichage p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modification p1
modifie(p1)
' affichage p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' affichage personne P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modification P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' affichage P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Les résultats obtenus sont les suivants :
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
On voit qu'il n'y a construction que d'un objet : celui de la personne p1 de la procédure Main et que l'objet a bien été modifié par la fonction modifie.
3.1.16. Un tableau de personnes
Un objet est une donnée comme une autre et à ce titre plusieurs objets peuvent être rassemblés dans un tableau :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
' pg de test
Module test
Sub Main()
' un tableau de personnes
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)
' affichage
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
L'instruction Dim amis(2) As personne crée un tableau de 3 éléments de type personne. Ces 3 éléments sont initialisés ici avec la valeur nothing, c.a.d. qu'ils ne référencent aucun objet. De nouveau, par abus de langage, on parle de tableau d'objets alors que ce n'est qu'un tableau de références d'objets. La création du tableau d'objets, qui est un objet lui-même ne crée aucun objet du type de ses éléments : il faut le faire ensuite. On obtient les résultats suivants :
3.2. L'héritage par l'exemple
3.2.1. Généralités
Nous abordons ici la notion d'héritage. Le but de l'héritage est de "personnaliser" une classe existante pour qu'elle satisfasse à nos besoins. Supposons qu'on veuille créer une classe enseignant : un enseignant est une personne particulière. Il a des attributs qu'une autre personne n'aura pas : la matière qu'il enseigne par exemple. Mais il a aussi les attributs de toute personne : prénom, nom et âge. Un enseignant fait donc pleinement partie de la classe personne mais a des attributs supplémentaires. Plutôt que d'écrire une classe enseignant à partir de rien, on préfèrerait reprendre l'acquis de la classe personne qu'on adapterait au caractère particulier des enseignants. C'est le concept d'héritage qui nous permet cela. Pour exprimer que la classe enseignant hérite des propriétés de la classe personne, on écrira :
Public Class enseignant
Inherits personne
On notera la syntaxe particulière sur deux lignes. La classe personne est appelée la classe parent (ou mère) et la classe enseignant la classe dérivée (ou fille). Un objet enseignant a toutes les qualités d'un objet personne : il a les mêmes attributs et les mêmes méthodes. Ces attributs et méthodes de la classe parent ne sont pas répétées dans la définition de la classe fille : on se contente d'indiquer les attributs et méthodes rajoutés par la classe fille. Nous supposons que la classe personne est définie comme suit :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' classe personne
Public Class personne
' attributs de classe
Private Shared _nbPersonnes As Long = 0
' attributs d'instance
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructeurs
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' une personne de plus
_nbPersonnes += 1
' construction
Me._prenom = P
Me._nom = N
Me._age = age
' suivi
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' une personne de plus
_nbPersonnes += 1
' construction
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' suivi
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' propriété de classe
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propriétés d'instance
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)
' age valide ?
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
La méthode identifie a été remplacée par la propriété identité en lecture seule et qui identifie la personne. Nous créons une classe enseignant héritant de la classe personne :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Public Class enseignant
Inherits personne
' attributs
Private _section As Integer
' constructeur
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
' suivi
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriété 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 enseignant rajoute aux méthodes et attributs de la classe personne :
- un attribut section qui est le n° de section auquel appartient l'enseignant dans le corps des enseignants (une section par discipline en gros)
- un nouveau constructeur permettant d'initialiser tous les attributs d'un enseignant
La déclaration
Public Class enseignant
Inherits personne
indique que la classe enseignant dérive de la classe personne.
3.2.2. Construction d'un objet enseignant
Le constructeur de la classe enseignant est le suivant :
' constructeur
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
' suivi
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
La déclaration
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
déclare que le constructeur reçoit quatre paramètres P, N, age, section. Elle doit en passer trois (P,N,age) à sa classe de base, ici la classe personne. On sait que cette classe a un constructeur personne(string, string, int) qui va permettre de construire une personne avec les paramètres passsés (P,N,age). La classe [enseignant] passe les paramètres (P,N,age) à sa classe de base de la façon suivante :
MyBase.New(P, N, age)
Une fois la construction de la classe de base terminée, la construction de l'objet enseignant se poursuit par l'exécution du corps du constructeur :
Me._section = section
En résumé, le constructeur d'une classe dérivée :
- passe à sa classe de base les paramètres dont elle a besoin pour se construire
- utilise les autres paramètres pour initialiser les attributs qui lui sont propres
On aurait pu préférer écrire :
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
' suivi
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
C'est impossible. La classe personne a déclaré privés (private) ses trois champs _prenom, _nom et _age. Seuls des objets de la même classe ont un accès direct à ces champs. Tous les autres objets, y compris des objets fils comme ici, doivent passer par des méthodes publiques pour y avoir accès. Cela aurait été différent si la classe personne avait déclaré protégés (protected) les trois champs : elle autorisait alors des classes dérivées à avoir un accès direct aux trois champs. Dans notre exemple, utiliser le constructeur de la classe parent était donc la bonne solution et c'est la méthode habituelle : lors de la construction d'un objet fils, on appelle d'abord le constructeur de l'objet parent puis on complète les initialisations propres cette fois à l'objet fils (section dans notre exemple).
Compilons les classes personne et enseignant dans des assemblages :
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
On remarquera que pour compiler la classe fille enseignant, il a fallu référencer le fichier personne.dll qui contient la classe personne. Tentons un premier programme de test :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Ce programme ce contente de créer un objet enseignant (new) et de l'identifier. La classe enseignant n'a pas de méthode identité mais sa classe parent en a une qui de plus est publique : elle devient par héritage une méthode publique de la classe enseignant. Les résultats obtenus sont les suivants :
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)
On voit que :
- un objet personne a été construit avant l'objet enseignant
- l'identité obtenue est celle de l'objet personne
3.2.3. Surcharge d'une méthode ou d'une propriété
Dans l'exemple précédent, nous avons eu l'identité de la partie personne de l'enseignant mais il manque certaines informations propres à la classe enseignant (la section). On est donc amené à écrire une propriété permettant d'identifier l'enseignant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Public Class enseignant
Inherits personne
' attributs
Private _section As Integer
' constructeur
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
' suivi
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propriété section
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' surcharge propriété identité
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
La méthode identite de la classe enseignant s'appuie sur la méthode identite de sa classe mère (MyBase.identite) pour afficher sa partie "personne" puis complète avec le champ _section qui est propre à la classe enseignant. Notons la déclaration de la propriété identite :
Public Shadows ReadOnly Property identite() As String
qui indique que la propriété identite "cache" la méthode de même nom qui pourrait exister dans la classe parent. Soit un objet enseignant E. Cet objet contient en son sein un objet personne :
![]() |
La propriété identité est définie à la fois dans la classe enseignant et sa classe mère personne. Dans la classe fille enseignant, la propriété identite doit être précédée du mot clé shadows pour indiquer qu'on redéfinit une nouvelle propriété identite pour la classe enseignant.
Public Shadows ReadOnly Property identite() As String
La classe enseignant dispose maintenant de deux propriétés identite :
- celle héritée de la classe parent personne
- la sienne propre
Si E est un ojet enseignant, E.identite désigne la méthode identite de la classe enseignant. On dit que la propriété identite de la classe mère est "redéfinie" par la propriété identite de la classe fille. De façon générale, si O est un objet et M une méthode, pour exécuter la méthode O.M, le système cherche une méthode M dans l'ordre suivant :
- dans la classe de l'objet O
- dans sa classe mère s'il en a une
- dans la classe mère de sa classe mère si elle existe
- etc…
L'héritage permet donc de redéfinir dans la classe fille des méthodes/propriétés de même nom dans la classe mère. C'est ce qui permet d'adapter la classe fille à ses propres besoins. Associée au polymorphisme que nous allons voir un peu plus loin, la surcharge de méthodes/propriétés est le principal intérêt de l'héritage. Considérons le même exemple que précédemment :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Les résultats obtenus sont cette fois les suivants :
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. Le polymorphisme
Considérons une lignée de classes : C0 C1 C2 … Cn où Ci Cj indique que la classe Cj est dérivée de la classe Ci. Cela entraîne que la classe Cj a toutes les caractéristiques de la classe Ci plus d'autres. Soient des objets Oi de type Ci. Il est légal d'écrire :
En effet, par héritage, la classe Cj a toutes les caractéristiques de la classe Ci plus d'autres. Donc un objet Oj de type Cj contient en lui un objet de type Ci. L'opération
fait que Oi est une référence à l'objet de type Ci contenu dans l'objet Oj.
Le fait qu'une variable Oi de classe Ci puisse en fait référencer non seulement un objet de la classe Ci mais en fait tout objet dérivé de la classe Ci est appelé polymorphisme : la faculté pour une variable de référencer différents types d'objets. Prenons un exemple et considérons la fonction suivante indépendante de toute classe :
Sub affiche(ByVal p As personne)
On pourra aussi bien écrire
que
Dans ce dernier cas, le paramètre formel de type personne de la fonction affiche va recevoir une valeur de type enseignant. Comme le type enseignant dérive du type personne, c'est légal.
3.2.5. Redéfinition et polymorphisme
Complétons notre procédure affiche :
Sub affiche(ByVal p As personne)
' affiche identité de p
Console.Out.WriteLine(p.identite)
End Sub
La méthode p.identite rend une chaîne de caractères identifiant l'objet personne. Que se passe-t-il dans le cas de notre exemple précédent dans le cas d'un objet enseignant :
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Regardons l'exemple suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
' un enseignant
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' une personne
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' affiche
Sub affiche(ByVal p As personne)
' affiche identité de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Les résultats obtenus sont les suivants :
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'exécution montre que l'instruction p.identite a exécuté à chaque fois la propriété identite d'une personne, la personne contenue dans l'enseignant e, puis la personne p elle-même. Elle ne s'est pas adaptée à l'objet réellement passé en paramètre à affiche. On aurait préféré avoir l'identité complète de l'enseignant e. Il aurait fallu pour cela que la notation p.identite référence la propriété identite de l'objet réellement pointé par p plutôt que la propriété identite de partie "personne" de l'objet réellement pointé par p. Il est possible d'obtenir ce résultat en déclarant identite comme une propriété redéfinissable (overridable) dans la classe de base personne :
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
Le mot clé overridable fait de identite une propriété réfinissable ou virtuelle. Ce mot clé peut s'appliquer également aux méthodes. Les classes filles qui redéfinissent une propriété ou méthode virtuelle doivent utiliser alors le mot clé overrides au lieu de shadows pour qualifier leur propriété/méthode redéfinie. Ainsi dans la classe enseignant, la propriété identite est définie comme suit :
' surcharge propriété identité
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
Le programme de test :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
' un enseignant
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' une personne
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' affiche
Sub affiche(ByVal p As personne)
' affiche identité de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
produit alors les résultats suivants :
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)
Cette fois-ci, on a bien eu l'identité complète de l'enseignant. Redéfinissons maintenant une méthode plutôt qu'une propriété. La classe object est la classe "mère" de toutes les classes VB.NET. Ainsi lorsqu'on écrit :
on écrit implicitement :
La classe object définit une méthode virtuelle ToString :

La méthode ToString rend le nom de la classe à laquelle appartient l'objet comme le montre l'exemple suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test2
Sub Main()
' un enseignant
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' une personne
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Les résultats produits sont les suivants :
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
On remarquera que bien que nous n'ayons pas redéfini la méthode ToString dans les classes personne et enseignant, on peut cependant constater que la méthode ToString de la classe object est quand même capable d'afficher le nom réel de la classe de l'objet. Redéfinissons la méthode ToString dans les classes personne et enseignant :
' ToString
Public Overrides Function ToString() As String
' on rend la propriété identite
Return identite
End Function
La définition est la même dans les deux classes. Considérons le programme de test suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
' un enseignant
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' une personne
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' affiche
Sub affiche(ByVal p As personne)
' affiche identité de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Les résultats d'exécution sont les suivants :
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. Définir un indexeur pour une classe
Considérons la classe [ArrayList] prédéfinie dans la plate-forme .NET. Cette classe permet de mémoriser des objets dans une liste. Elle appartient à l'espace de noms [System.Collections]. Dans l'exemple suivant, nous utilisons cette classe pour mémoriser une liste de personnes (au sens large) :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' pour ajouter une personne à la liste
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' un indexeur
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' un autre indexeur
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' on recherche la personne de nom 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
' rend (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' on parcourt le tableau dynamique
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' dernier élément
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Donnons quelques informations sur certaines propriétés et méthodes de la classe [ArrayList] :
attribut donnant le nombre d'éléments de la liste | |
méthode permettant d'ajouter un objet à la liste | |
méthode donnant l'élément i de la liste |
Nous remarquons que pour obtenir l'élément n° i de liste, nous n'avons pas écrit [liste.Item(i)] mais directement [liste(i)], ce qui à priori semble erroné. Ceci est néanmoins possible parce que la classe [ArrayList] définit une propriété par défaut [Item] selon la syntaxe analogue à la suivante :
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Lorsque le compilateur rencontre la notation [liste(i)], il cherche si la classe [ArrayList] a défini une propriété avec la signature suivante :
Default Public Property Proc(ByVal var As Integer) As Type
Ici, il trouvera la procédure [Item]. Il traduira alors l'écriture [liste(i)] en [liste.Item(i)]. On appelle la propriété [Item], la propriété indexée par défaut de la classe [ArrayList]. L'exécution du programme précédent donne les résultats suivants :
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)
Créons une classe appelé [listeDePersonnes] qui serait une liste de personnes, donc une liste particulière qu'il semble naturel de dériver de la classe [ArrayList] :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Imports System.Collections
' classe listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' pour ajouter une personne à la liste
Public Shadows Sub Add(ByVal p As Object)
' il faut que p soit une personne
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' un indexeur
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
' rend (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' on parcourt le tableau dynamique
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' dernier élément
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
La classe présente les méthodes et propriétés suivantes :
rend une chaîne de caractères "représentant" le contenu de la liste | |
méthode permettant d'ajouter une personne à la liste | |
propriété indexée par défaut donnant la personne i de la liste |
Considérons les points nouveaux :
Une nouvelle méthode [Add] est créée.
' pour ajouter une personne à la liste
Public Shadows Sub Add(ByVal p As Object)
' il faut que p soit une personne
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
Il en existe déjà une dans la classe parent [ArrayList] avec la même signature, d'où le mot clé [Shadows] pour indiquer que la nouvelle procédure remplace celle de la classe parent. La méthode [Add] de la classe fille vérifie que l'objet ajouté est bien de type [personne] ou dérivé avec la fonction [TypeOf]. Si ce n'est pas le cas, une exception est lancée avec l'instruction [Throw]. Si l'objet ajouté est bien de type [personne], la méthode [Add] de la classe de base est utilisée pour faire l'ajout.
Une propriété indexée est créée :
' un indexeur
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
Il existe déjà une propriété par défaut appelée [Item] dans la classe de base avec la même signature. Aussi est-on obligé d'utiliser le mot clé [Shadows] pour indiquer que la nouvelle propriété indexée [Index] va "cacher" celle de la classe de base [Item]. On notera que ceci est vrai même si les deux propriétés ne portent pas le même nom. La propriété [Index] permet de référencer la personne n° i de la liste. Elle s'appuie sur la propriété [Item] de la classe de base pour avoir accès à l'élément n° i de l'objet [ArrayList] sous-jacent. Des changements de type sont opérés pour tenir compte que la propriété [Item] travaille avec des éléments de type [Object] alors que la propriété [Index] travaille avec des éléments de type [personne].
Enfin, nous redéfinissons (Overrides) la méthode [ToString] de la classe [ArrayList] :
' toString
Public Overrides Function ToString() As String
' rend (él1, él2, ..., éln)
Dim liste As String = "("
Dim i As Integer
' on parcourt le tableau dynamique
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' dernier élément
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Cette méthode rend une chaîne de caractères de la forme "(e1,e2,...,en)" où ei sont les éléments de la liste. On remarquera la notation [Me(i)] qui désigne l'élément n° i de l'objet courant [Me]. C'est la propriété indexée par défaut qui est utilisée. Ainsi [Me(i)] est équivalent à [Me.Index(i)].
Le code de la classe est placé dans le fichier [lstpersonnes2.vb] et compilé :
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
Un programme de test est construit :
' options
Option Explicit On
Option Strict On
' espaces de noms
Imports System
Imports System.Collections
Module test
Sub Main()
' création d'une liste vide de personnes
Dim liste As listeDePersonnes = New listeDePersonnes
' création de personnes
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)
' remplissage de la liste
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' affichage de la liste
Console.Out.WriteLine(liste.ToString)
' ajout d'un objet différent de personne
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Il est compilé :
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
puis exécuté :
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
On pourrait vouloir écrire
où l serait de type [listeDePersonnes]. Ici, on veut indexer la liste l non plus par un n° d'élément mais par un nom de personne. Pour cela on définit une nouvelle propriété indexée par défaut :
' un autre indexeur
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' on recherche la personne de nom 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 première ligne
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indique qu'on crée de nouveau une propriété indexée par défaut. Toutes les propriétés par défaut doivent porter le meme nom, ici [Index]. La nouvelle propriété [Index] indexe la classe listeDePersonnes par une chaîne de caractères N. Le résultat de listeDePersonnes(N) est un entier. Cet entier sera la position dans la liste, de la personne portant le nom N ou -1 si cette personne n'est pas dans la liste. On ne définit que la propriété get interdisant ainsi l'écriture listeDePersonnes ("nom")=valeur qui aurait nécessité la définition de la propriété set. D'où le mot clé [ReadOnly]. Le mot clé [Shadows] est nécessaire pour cacher la propriété par défaut de la classe de base (bien qu'elle n'ait pas la même signature).
Dans le corps du get, on parcourt la liste des personnes à la recherche du nom N passé en paramètre. Si on le trouve en position i, on renvoie i sinon on renvoie -1.
Un nouveau programme de test pourrait être le suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' pg de test
Module test
Sub Main()
' une liste de personnes
Dim l As New listeDePersonnes
' ajout de personnes
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' affichage
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' changement élément 1
l(1) = New personne("sylvie", "cachan", 5)
' affichage élément 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' affichage liste l
Console.Out.WriteLine(("l=" + l.ToString))
' recherche de personnes
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'exécution donne les résultats suivants :
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. Les structures
La structure VB.NET est directement issue de la structure du langage C et est très proche de la classe. Une structure est définie comme suit :
Structure spersonne
' attributs
...
' propriétés
...
' constructeurs
...
' méthodes
End Structure
Il y a, malgré une similitude de déclaration, des différences importantes entre classe et structure. La notion d'héritage n'existe par exemple pas avec les structures. Si on écrit une classe qui ne doit pas être dérivée, quelles sont les différences entre structure et classe qui vont nous aider à choisir entre les deux ? Aidons-nous de l'exemple suivant pour le découvrir :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' structure spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' classe cpersonne
Class cpersonne
Public nom As String
Public age As Integer
End Class
' une module de test
Public Module test
Sub Main()
' une spersonne p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' une spersonne p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 est modifié
sp2.nom = "nicole"
sp2.age = 30
' vérification sp1 et sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' une cpersonne cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' une cpersonne P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 est modifié
cp2.nom = "nicole"
cp2.age = 30
' vérification P1 et P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Si on exécute ce programme, on obtient les résultats suivants :
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)
Là où dans les pages précédentes de ce chapitre on utilisait une classe personne, nous utilisons maintenant une structure spersonne :
' structure spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
La déclaration
Dim sp1 As spersonne
crée une structure (nom,age) et la valeur de sp1 est cette structure elle-même.
La déclaration
Dim cp1 As New cpersonne
crée un objet [cpersonne] (grosso modo l'équivalent de notre structure) et cp1 est alors l'adresse (la référence) de cet objet.
Résumons
- dans le cas de la structure, la valeur de sp1 est la structure elle-même
- dans le cas de la classe, la valeur de p1 est l'adresse de l'objet créé
![]() |
![]() |
Lorsque dans le programme on écrit
Dim sp2 As spersonne = sp1
une nouvelle structure (nom,age) est créée et initialisée avec la valeur de p1 donc la structure elle-même.
![]() |
La structure de sp1 est donc dupliquée dans sp2. C'est une recopie de valeur.
L'instruction
Dim cp2 As cpersonne = cp1
agit différemment. La valeur de cp1 est recopiée dans cp2, mais comme cette valeur est en fait l'adresse de l'objet, celui-ci n'est pas dupliqué. Il a simplement deux références sur lui :
![]() |
Dans le cas de la structure, si on modifie la valeur de sp2 on ne modifie pas la valeur de sp1, ce que montre le programme. Dans le cas de l'objet, si on modifie l'objet pointé par cp2, celui pointé par cp1 est modifié puisque c'est le même. C'est ce que montrent également les résultats du programme.
On retiendra donc de ces explications que :
- la valeur d'une variable de type structure est la structure elle-même
- la valeur d'une variable de type objet est l'adresse de l'objet pointé
Une fois cette différence fondamentale comprise, la structure se montre très proche de la classe comme le montre le nouvel exemple suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' structure personne
Structure personne
' attributs
Private _nom As String
Private _age As Integer
' propriétés
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
' Constructeur
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' un module de test
Module test
Sub Main()
' une personne p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' une personne p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 est modifié
p2.nom = "nicole"
p2.age = 30
' vérification p1 et p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
On obtient les résultats d'exécution suivants :
La seule notable différence ici entre structure et classe, c'est qu'avec une classe, les objets p1 et p2 auraient eu la même valeur à la fin du programme, celle de p2.
3.5. Les interfaces
Une interface est un ensemble de prototypes de méthodes ou de propriétés qui forme un contrat. Une classe qui décide d'implémenter une interface s'engage à fournir une implémentation de toutes les méthodes définies dans l'interface. C'est le compilateur qui vérifie cette implémentation. Voici par exemple la définition de l'interface Istats :
Toute classe implémentant cette interface sera déclarée comme
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
Les méthodes [moyenne] et [écartType] devront être définies dans la classe C. Considérons le code suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' structure
Public Structure élève
Public _nom As String
Public _note As Double
' constructeur
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' classe notes
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
' constructeur
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' mémorisation élèves & matière
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
' on concatène toutes les notes
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'dernière note
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' fin
Return valeur
End Function
End Class
La classe notes rassemble les notes d'une classe dans une matière :
Public Class notes
' attribut
Protected _matière As String
Protected _élèves() As élève
Les attributs sont déclarés protected pour être accessibles d'une classe dérivée. Le type élève est une structure mémorisant le nom de l'élève et sa note dans la matière :
Public Structure élève
Public _nom As String
Public _note As Double
' constructeur
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Nous décidons de dériver cette classe notes dans une classe notesStats qui aurait deux attributs supplémentaires, la moyenne et l'écart-type des notes :
Public Class notesStats
Inherits notes
Implements Istats
' attributs
Private _moyenne As Double
Private _écartType As Double
La classe notesStats implémente l'interface Istats suivante :
Cela signifie que la classe notesStats doit avoir deux méthodes appelées moyenne et écartType avec la signature indiquée dans l'interface Istats. La classe notesStats est la suivante :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' attributs
Private _moyenne As Double
Private _écartType As Double
' constructeur
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' calcul moyenne des notes
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
' écart-type
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
' méthodes de l'interface Istats
Public Function moyenne() As Double Implements Istats.moyenne
' rend la moyenne des notes
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' rend l'écart-type
Return _écartType
End Function
End Class
La moyenne _moyenne et l'écart-type _ecartType sont calculés dès la construction de l'objet. Aussi les méthodes moyenne et écartType n'ont-elles qu'à rendre la valeur des attributs _moyenne et _ecartType. Les deux méthodes rendent -1 si le tableau des élèves est vide.
La classe de test suivante :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Module test
Sub Main()
' qqs élèves & notes
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' qu'on enregistre dans un objet notes
Dim anglais As New notes("anglais", ELEVES)
' et qu'on affiche
Console.Out.WriteLine((anglais.ToString))
' idem avec moyenne et écart-type
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
donne les résultats :
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 aurait très bien pu implémenter les méthodes moyenne et écartType pour elle-même sans indiquer qu'elle implémentait l'interface Istats. Quel est l'intérêt des interfaces ? C'est le suivant : une fonction peut admettre pour paramètre formel une donnée ayant le type d'une interface I. Tout objet d'une classe C implémentant l'interface I pourra alors être paramètre effectif de cette fonction. Considérons l'exemple suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' une interface Iexemple
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
' une 1ère classe
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'une 2ième classe
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
L'interface Iexemple définit deux méthodes ajouter et soustraire. Les classes classe1 et classe2 implémentent cette interface. On remarquera que ces classes ne font rien d'autre, ceci par souci de simplification de l'exemple. Maintenant considérons l'exemple suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' classe de test
Module test
'calculer
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' la fonction Main
Sub Main()
' création de deux objets classe1 et classe2
Dim c1 As New classe1
Dim c2 As New classe2
' appels de la fonction statique calculer
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
La fonction calculer admet pour paramètre un élément de type Iexemple. Elle pourra donc recevoir pour ce paramètre aussi bien un objet de type classe1 que de type classe2. C'est ce qui est fait dans la procédure Main avec les résultats suivants :
On voit donc qu'on a là une propriété proche du polymorphisme vu pour les classes. Si un ensemble de classes Ci non liées entre-elles par héritage (donc on ne peut utiliser le polymorphisme de l'héritage) présente un ensemble de méthodes de même signature, il peut être intéressant de regrouper ces méthodes dans une interface I dont hériteraient toutes les classes concernées. Des instances de ces classes Ci peuvent alors être utilisées comme paramètres de fonctions admettant un paramètre de type I, c.a.d. des fonctions n'utilisant que les méthodes des objets Ci définies dans l'interface I et non les attributs et méthodes particuliers des différentes classes Ci. Notons enfin que l'héritage d'interfaces peut être multiple, c.a.d. qu'on peut écrire
Public Class classe
Implements I1,I2,...
où les Ij sont des interfaces.
3.6. Les espaces de noms
Pour écrire une ligne à l'écran, nous utilisons l'instruction
Si nous regardons la définition de la classe Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
on découvre qu'elle fait partie de l'espace de noms System. Cela signifie que la classe Console devrait être désignée par System.Console et on devrait en fait écrire :
On évite cela en utilisant une clause imports :
On dit qu'on importe l'espace de noms System avec la clause imports. Lorsque le compilateur va rencontrer le nom d'une classe (ici Console) il va chercher à la trouver dans les différents espaces de noms importés par les clauses imports. Ici il trouvera la classe Console dans l'espace de noms System. Notons maintenant la seconde information attachée à la classe Console :
Assembly: Mscorlib (in Mscorlib.dll)
Cette ligne indique dans quelle "assemblage" se trouve la définition de la classe Console. Lorsqu'on compile en-dehors de Visual Studio.NET et qu'on doit donner les références des différentes dll contenant les classes que l'on doit utiliser cette information peut s'avérer utile. On rappelle que pour référencer les dll nécessaires à la compilation d'une classe, on écrit :
Lorsqu'on crée une classe, on peut la créer à l'intérieur d'un espace de noms. Le but de ces espaces de noms est d'éviter les conflits de noms entre classes lorsque celles-ci sont vendues par exemple. Considérons deux entreprises E1 et E2 distribuant des classes empaquetées respectivement dans les dll, E1.dll et E2.dll. Soit un client C qui achète ces deux ensembles de classes dans lesquelles les deux entreprises ont défini toutes deux une classe personne. Le client C compile un programme de la façon suivante :
Si le source prog.vb utilise la classe personne, le compilateur ne saura pas s'il doit prendre la classe personne de E1.dll ou celle de E2.dll. Il signalera une erreur. Si l'entreprise E1 prend soin de créer ses classes dans un espace de noms appelé E1 et l'entreprise E2 dans un espace de noms appelé E2, les deux classes personne s'appelleront alors E1.personne et E2.personne. Le client devra employer dans ses classes soit E1.personne, soit E2.personne mais pas personne. L'espace de noms permet de lever l'ambiguïté. Pour créer une classe dans un espace de noms, on écrit :
Namespace istia.st
Public Class personne
' définition de la classe
...
end Class
end Namespace
Pour l'exemple, créons dans un espace de noms notre classe personne étudiée précédemment. Nous choisirons istia.st comme espace de noms. La classe personne devient :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
' création de l'espace de nom istia.st
Namespace istia.st
Public Class personne
' attributs
Private prenom As String
Private nom As String
Private age As Integer
' méthode
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
' méthode
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Cette classe est compilée dans personne.dll :
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Maintenant utilisons la classe personne dans une classe de test :
' options
Option Strict On
Option Explicit On
' espaces de noms
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
Pour éviter d'écrire
Dim p1 As New istia.st.personne
nous avons importé l'espace de noms istia.st avec une clause imports :
Imports istia.st
Maintenant compilons le programme de test :
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
Cela produit un fichier test.exe qui exécuté donne les résultats suivants :
3.7. L'exemple IMPOTS
On reprend le calcul de l'impôt déjà étudié dans le chapitre précédent et on le traite en utilisant une classe. Rappelons le problème :
On se place dans le cas simplifié d'un contribuable n'ayant que son seul salaire à déclarer :
- on calcule le nombre de parts du salarié nbParts=nbEnfants/2 +1 s'il n'est pas marié, nbEnfants/2+2 s'il est marié, où nbEnfants est son nombre d'enfants.
- s'il a au moins trois enfants, il a une demie part de plus
- on calcule son revenu imposable R=0.72*S où S est son salaire annuel
- on calcule son coefficient familial QF=R/nbParts
- on calcule son impôt I. Considérons le tableau suivant :
12620.0 | 0 | 0 |
13190 | 0.05 | 631 |
15640 | 0.1 | 1290.5 |
24740 | 0.15 | 2072.5 |
31810 | 0.2 | 3309.5 |
39970 | 0.25 | 4900 |
48360 | 0.3 | 6898.5 |
55790 | 0.35 | 9316.5 |
92970 | 0.4 | 12106 |
127860 | 0.45 | 16754.5 |
151250 | 0.50 | 23147.5 |
172040 | 0.55 | 30710 |
195000 | 0.60 | 39312 |
0 | 0.65 | 49062 |
Chaque ligne a 3 champs. Pour calculer l'impôt I, on recherche la première ligne où QF<=champ1. Par exemple, si QF=23000 on trouvera la ligne
L'impôt I est alors égal à 0.15*R - 2072.5*nbParts. Si QF est tel que la relation QF<=champ1 n'est jamais vérifiée, alors ce sont les coefficients de la dernière ligne qui sont utilisés. Ici :
ce qui donne l'impôt I=0.65*R - 49062*nbParts.
La classe impot sera définie comme suit :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Public Class impot
' les données nécessaires au calcul de l'impôt
' proviennent d'une source extérieure
Private limites(), coeffR(), coeffN() As Decimal
' constructeur
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' on vérifie que les 3 tableaux ont la même taille
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
' c'est bon
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' calcul de l'impôt
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' calcul du nombre de parts
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
' calcul revenu imposable & Quotient familial
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' calcul de l'impôt
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
Un objet impôt est créé avec les données permettant le calcul de l'impôt d'un contribuable. C'est la partie stable de l'objet. Une fois cet objet créé, on peut appeler de façon répétée sa méthode calculer qui calcule l'impôt du contribuable à partir de son statut marital (marié ou non), son nombre d'enfants et son salaire annuel. Un programme de test pourait être le suivant :
' options
Option Strict On
Option Explicit On
' espaces de noms
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' programme interactif de calcul d'impôt
' l'utilisateur tape trois données au clavier : marié nbEnfants salaire
' le programme affiche alors l'impôt à payer
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"
' tableaux de données nécessaires au calcul de l'impôt
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}
' création d'un objet impôt
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
' boucle infinie
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' on demande les paramètres du calcul de l'impôt
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()
' qq chose à faire ?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' vérification du nombre d'arguments dans la ligne saisie
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
' vérification de la validité des paramètres
If Not erreur Then
' marié
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
' salaire
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' si les paramètres sont corrects - on calcule l'impôt
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
Voici un exemple d'exécution du programme précédent :
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 :





