Tout code exécutable doit être écrit à l'intérieur d'une procédure ou d'une fonction, c’est connu. Ce qu'il faut encore dire, c'est que toute procédure ou fonction doit être écrite à l'intérieur d'un module, d'une structure ou d'une classe. Les exemples de programmation à l'intérieur d'un module sont déjà nombreux. En ce qui concerne la structure, elle a été étudiée en tant que moyen d'organisation des données et il est nécessaire de compléter l'information à son sujet. Une classe peut être définie en dehors de toute autre structure de programmation, auquel cas elle est obligatoirement
Public, ou définie comme sous classe à l'intérieur d'une autre classe, où le programmeur peut lui donner la portée qu'il souhaite. Elle peut également être définie à l'intérieur d'un module où elle peut être Public, Friend ou Private, mais dans la mesure où un module n'est pas accessible par instanciation ou héritage dans une application cliente, les portées Public et Friend sont tout à fait équivalentes. Une classe peut aussi être définie à l'intérieur d'une structure. Deux attributs peuvent être associés à la déclaration de portée de la classe pour préciser son comportement en matière d'héritage : MustInherit et NotInheritable.
MustInherit Indique que la classe sert de classe de base dans une relation d'héritage et elle ne peut être instanciée. En général, dans une telle classe, seules les déclarations des méthodes sont définies. Le contenu doit être rédigé dans les classes dérivées.
NotInheritable Indique que la classe est la dernière de la hiérarchie. Aucune autre classe ne peut en dériver.
Public Class MaClasse
' Inherits UneClasseMere ' Implements UneInterface ' Implements UneAutreInterface
Public Function MonCinq() As Integer Return 5 End Function End Class Module MonModule Sub Main() Dim MC As MaClasse MC = New MaClasse Console.WriteLine(MC.MonCinq) Console.ReadLine() End Sub End Module
La classe MaClasse écrite en dehors de toute autre structure est bien Public et elle expose une méthode, la fonction
MonCinq. Les lignes en commentaires ne sont évidemment pas utiles ici, sauf pour présenter l'endroit où il faut, le cas échéant, déclarer une classe mère et des interfaces. L'utilisation du mot clé Inherits précède la classe dont hérite celle en cours. Il ne peut y avoir qu'une seule ligne de ce type dans une classe. Le mot clé Implements précède le nom d'une interface souhaitée. Il peut y en avoir plusieurs par classe. Une interface ressemble un peu à une classe mais elle contient uniquement des descriptions de propriétés et de méthodes, sans code à l'intérieur, ni même de fin de code (End Sub, EndFunction). Implémenter une interface revient à s'engager à en écrire le contenu pour toutes les méthodes et propriétés. L'usage de l'interface est étudié plus loin.
L'application cliente de MaClasse est le module MonModule. Dans sa procédure Main, la déclaration Dim MC As MaClasse attribue une variable pour la manipulation de l'objet. L'affectation de cette variable est réalisée par l'appel du constructeur de la classe qui réalise l'instanciation de l'objet, c'est-à-dire sa création en mémoire. Il faut remarquer au passage que le code du constructeur New de la classe n'a pas été écrit. Dans ce cas, le compilateur en crée un d'office ou plus exactement, il en recherche un en remontant la généalogie des classes jusqu’à la classe Object s’il le faut.
L'exemple suivant illustre la création d'une sous classe et celle d'une classe à l'intérieur d'un module, ainsi que leur instanciation.
Public NotInheritable Class MaClasse ' Nul ne pourra en hériter
Private Shared UneValeur As Integer ' Une variable de classe
Public Function MaFonction1() As Integer ' Une méthode
UneValeur = 5
Return UneValeur ' Retourne la valeur de la variable de classe
End Function
Public Function MaFonction2() As Integer ' Une autre méthode
Dim C As UneSousClasse
C = New UneSousClasse ' Instanciation d'une sous classe
Return C.UneFonction / 2 ' Retourne la valeur reçue d'une méthode
End Function ' de la sous classe après l'avoir divisée
Public Class UneSousClasse ' Définition de la sous classe UneSousClasse
Public Function UneFonction() As Integer UneValeur = 10
Return UneValeur * 2 ' La méthode retourne un entier
End Function
End Class
End Class
Module MonModule
Public Class ClasseModule ' Définition d'une classe dans le module
Public Function UneAutreFonction() As String
Return "MomMessage" ' La méthode retourne une chaîne
End Function
End Class
Sub Main()
Dim MC As MaClasse ' Une variable pour MaClasse
Dim MSC As MaClasse.UneSousClasse ' Une variable pour accéder à la sous classe
Dim MCM As ClasseModule ' Une variable pour ClasseModule
MC = New MaClasse ' Création d'un objet MaClasse
MSC = New MaClasse.UneSousClasse ' Création d'un objet UneSousClasse
MCM = New ClasseModule ' Création d'un objet ClasseModule
Console.WriteLine(MC.MaFonction1) ' Affiche 5
Console.WriteLine(MC.MaFonction2) ' Affiche 10
Console.WriteLine(MSC.UneFonction) ' Affiche 20
Console.WriteLine(MCM.UneAutreFonction) ' Affiche MonMessage
Console.ReadLine() End Sub
End Module
Tout fonctionne aussi bien, dans les exemples précédents quand le mot clé Class est remplacé par le mot clé
Structure. Cependant, il y a des différences et notamment celles-ci :
Un type Structure est de type valeur alors qu'une classe est de type référence. Un type Structure doit déclarer au moins une variable membre ou un événement.
Un type Structure peut déclarer un constructeur, mais alors il doit obligatoirement être paramétré. Un type Structure ne peut pas servir de base pour un autre qui en hériterait.
Un type Structure ne peut pas spécifier une classe dont il hériterait. Un type Structure ne peut pas déclarer un destructeur.
Le premier exemple :
Public Structure MaStructure
' Implements UneInterface ' L'implémentation d'interfaces est permise
Dim UneVariable As Integer ' Une structure doit contenir au moins une
Public Function MonCinq() As Integer ' variable membre.
Return 5 End Function End Structure Module MonModule Sub Main() Dim MS As MaStructure
' MS = New MaStructure ' Instanciation superflue. Toutefois, les
Console.WriteLine(MS.MonCinq) ' membres de type référence doivent être
Console.ReadLine() ' initialisés
End Sub End Module
Le deuxième exemple :
Public Structure MaStructure
Private Shared UneValeur As Integer ' Ne compte pas comme variable membre
Private XMS As Integer ' Voici une variable membre
Public Function MaFonction() As Integer ' Une fonction membre qui utilise la fonction
Dim C As UneSousStructure ' membre d'une sous structure
' C = New UneSousStructure ' Instanciation superflue.
Return C.UneFonction / 2 End Function
Public Structure UneSousStructure ' Définition de la sous structure et de ses
Dim XMSS As Integer ' membres, dont une fonction
Public Function UneFonction() As Integer UneValeur = 10
Return UneValeur * 2 End Function
End Structure
Public Class ClasseStructure ' Définition d'une classe dans la structure
Public Function UneFonction() As Integer ' et d'une méthode UneFonction
Dim X As Integer = 30 Return X End Function End Class End Structure Module MonModule
Public Structure StructureModule ' Définition d'une structure dans le module
Dim XSM As Integer ' et de ses membres, dont une fonction
Public Function UneAutreFonction() As String Return "MomMessage" End Function End Structure Sub Main() Dim MS As MaStructure Dim MSS As MaStructure.UneSousStructure Dim MSM As StructureModule Dim MCS As MaStructure.ClasseStructure
MCS = New MaStructure.ClasseStructure ' Une classe est un objet de type référence
Console.WriteLine(MS.MaFonction) ' Affiche 10
Console.WriteLine(MSS.UneFonction) ' Affiche 20
Console.WriteLine(MSM.UneAutreFonction) ' Affiche MonMessage
Console.WriteLine(MCS.UneFonction) ' Affiche 30
Console.ReadLine() End Sub
Un dernier exemple pour illustrer encore les similitudes existant entre les Class et les Structure, mais aussi l'une de leurs différences, ainsi que l'emploi d'un constructeur personnalisé.
Public Structure MaStructure ' Définition d'une structure MaStructure et
Public XMS As Integer ' de ses membres, soit une variable de type
Public Tab1() As Integer ' entier, un tableau d'entiers, une variable
Public Classe As ClasseEnStructure ' de type objet ClasseEnStructure
Public Sub New(ByVal NbEntiers As Integer) ' La structure déclare un constructeur dont
Tab1 = New Integer(NbEntiers -1) {} ' le rôle est de créer le tableau pour un
Classe = New ClasseEnStructure ' nombre d'entiers passé en paramètre et
End Sub ' de créer l'objet pour la variable Classe
Public Class ClasseEnStructure
Public Tab2 = New Integer(5) {} ' La classe expose un tableau pour 6 entiers
End Class End Structure
Public Class MaClasse ' Définition d'une classe MaClasse et
Public XMC As Integer ' de ses membres, soit une variable de type
Public Tab1() As Integer ' entier, un tableau d'entiers, une variable
Public Classe As ClasseEnClasse ' de type objet ClasseEnClasse
Public Sub New(ByVal NbEntiers As Integer) ' La structure déclare un constructeur dont
Tab1 = New Integer(NbEntiers -1) {} ' le rôle est de créer le tableau pour un
Classe = New ClasseEnClasse ' nombre d'entiers passé en paramètre et
End Sub ' de créer l'objet pour la variable Classe
Public Class ClasseEnClasse
Public Tab2 = New Integer(5) {} ' La classe expose un tableau pour 6 entiers
End Class End Class
Jusqu'ici, il n'y a pas de différences importantes entre les approches Structure et Class.
Module MonModule Sub Main()
Dim MS, MS2 As MaStructure ' Déclaration des variables pour MaStructure
Dim MC, MC2 As MaClasse ' Déclaration des variables pour MaClasse
MS = New MaStructure(5) ' Création d'une MaStructure en mémoire
MS.XMS = 123 ' Affectation d'un membre de MaStructure
MS.Tab1(1) = 10 ' Affectation d'un autre membre
MC = New MaClasse(5) ' Création d'une MaClasse en mémoire
MC.XMC = 456 ' Affectation d'un membre de MaClasse
MC.Tab1(1) = 100 ' Affectation d'un autre membre
MS2 = MS ' Affectation d'une autre MaStructure par MS
MS2.XMS = 321 ' Modification des valeurs de cette variable
MS2.Tab1(1) = 101 '
MC2 = MC ' Affectation d'une autre MaClasse par MC
MC2.XMC = 654 ' Modification des valeurs de cette variable
MC2.Tab1(1) = 1001 ' Console.WriteLine(MS.XMS) ' Affiche 123 Console.WriteLine(MS2.XMS) ' Affiche 321 Console.WriteLine(MS.Tab1(1)) ' Affiche 101 Console.WriteLine(MS2.Tab1(1)) ' Affiche 101 Console.WriteLine(MC.XMC) ' Affiche 654 Console.WriteLine(MC2.XMC) ' Affiche 654 Console.WriteLine(MC.Tab1(1)) ' Affiche 1001
Une différence !
Console.WriteLine(MC2.Tab1(1)) ' Affiche 1001 End Sub End ModuleLa seule différence constatée dans l'exemple précédent réside apparemment dans le fait que la variable de type valeur est bien traitée comme telle dans la structure et non dans la classe. En réalité, c'est la structure elle-même qui est de type valeur et lors de l'affectation de MS à MS2, toutes les valeurs de la première sont copiées dans la deuxième. Dans le cas de l'objet de type MaClasse, seule la référence de MC est copiée dans MC2 lors de l'affectation, et c'est bien normal puisqu'une classe est de type référence.
Dès lors, et en première approche, il est étonnant que les tableaux de la structure ne soient pas simplement copiés eux aussi, qu'ils soient traités comme dans la classe. Il faut se souvenir qu'un tableau est de type référence et que seule la référence est conservée dans la variable. Ainsi, tout s'explique.
Donc, dans la structure comme dans la classe, seule la référence d'un membre de type référence est conservée dans la variable correspondante et dans le cas de la structure, cette référence est bien copiée par l'affectation, tandis que dans le cas de la classe, seule la référence de l'instance de la classe elle-même est copiée.
Le choix entre Class et Structure est simple dès lors qu’est abordée la programmation orientée objet. Comme la structure ne peut hériter d'une classe et qu'aucune classe ou autre structure ne peut en dériver, il est évident qu'elle n'intègre pas le concept d'héritage de la POO, ni donc celui du polymorphisme. La structure est donc à rejeter de la POO, sauf pour organiser des lots de données sous la forme de types complexes définis par le programmeur.
La classe intègre tous les concepts de la POO, elle est maintenant le principal objet de ces pages.
Gestion de la mémoire
La déclaration d’un objet de type valeur provoque la réservation en mémoire de l’emplacement nécessaire pour stocker la ou les valeur(s) de cet objet. L’initialisation par défaut de la variable le représentant, est la valeur nulle selon le type. L'opération d'affectation entre objets de ce type copie toutes les valeurs de l'objet. Les valeurs de ce type sont stockées dans la pile ou stack. Les données de la pile sont dépilées et perdues quand l'application quitte la portée qui leur a été allouée. L'espace occupé dans la pile est celui nécessaire au stockage des valeurs des variables.
La déclaration d’un objet de type référence provoque la réservation en mémoire de l’emplacement nécessaire pour stocker l’adresse où sera stocké cet objet. L’initialisation par défaut de la variable le représentant, qui est donc un pointeur, est Nothing. Ce n'est qu'à la création effective de l'objet que cette variable reçoit l'adresse de l'objet.
Une variable de type référence doit être initialisée par des valeurs ou par l’usage de l’opérateur New pour permettre au compilateur d’évaluer et de réserver l’espace nécessaire en mémoire. Quand cette variable représente l'instance d'une classe, New, qui est le constructeur de toute classe, est le seul opérateur habilité à l'initialiser. L'opération d'affectation entre objets de ce type ne copie que l'adresse mémoire de l'objet, sa référence. Les références des objets sont stockées dans la pile et leurs valeurs dans le tas ou heap. Comme pour les objets de type valeur, les références dans la pile sont dépilées et perdues quand l'application quitte la portée qui leur a été allouée. Cependant, ce processus ne libère pas l'espace occupé dans le tas par les objets. Cette libération de la mémoire est réalisée automatiquement par le ramasse- miettes ou garbage collector. L'affectation de la valeur Nothing à une référence n'influence en rien l'occupation de la mémoire, aussi bien côté pile que côté tas. L'espace occupé dans la pile est celui nécessaire au stockage d'une adresse, soit quatre ou huit octets selon le système, et celui occupé dans le tas est l'espace nécessaire pour les données des objets.
Les codes, comme ceux des méthodes par exemple, sont stockés en d'autres endroits de la mémoire et n'affectent ni la pile, ni le tas.
Quelque part dans la pile Quelque part dans le tas
Opérations Etiquettes Adresses
supposées Valeurs Adresses supposées Valeurs … Dim XN As Integer XN 14000000H 0 XN = 5 XN 14000000H 5
Dim MC As MaClasse MC 13FFFFFCH Nothing
MC = New MaClasse MC 13FFFFFCH 10000000H 10000000H Début d'un objet
MaClasse Dim MC2 As MaClasse MC2 13FFFFF8H Nothing
MC2 = MC MC2 13FFFFF8H 10000000H
Dim T() As Integer T 13FFFFF4H Nothing
T = New Integer(5) {} T 13FFFFF4H 10001000H 10001000H Espace pour
6 entiers
T(1) = 7 T 13FFFFF4H 10001000H 10001004H 7