Cours
Préambule
Le CLR prend en charge 2 sortes de types définis par le CTS :
-
Les types valeurs
-
Les types références
Une des différences entre ces 2 types est leur gestion en mémoire.
Lorsque le CLR démarre, il alloue une zone de mémoire qu’il sépare en 2 parties distinctes :
La pile du thread (stack) et le tas (heap).
La pile (stack)
La pile d’éxécution est une pile de type LIFO (Last In First Out).
Elle est utilisée en tant que zone d’échange et de stockage temporaire entre le code et le microprocesseur.
Les instructions et les données sont placés un par un sur la pile en respectant un ordre précis d’exécution. Lorsqu’une instruction reconnue par le microprocesseur est placée surle haut de la pile, celui-ci dépile (enlève) cette instruction de la pile pour la traiter.
Si cette instruction nécessite 2 paramètres, le microprocesseur va dépiler les 2 valeurs situées sur le haut de la pile.
Si l’instruction traitée renvoie une valeur, celle-ci sera placée sur le haut de la pile.
Ex : addition de 10 + 9
ldc.i4.s 10
ldc.i4,s 9
add (addition) ou add.ovf (avec controle overflow)
Ces instructions génèrent l’état suivant sur la pile:
Par défaut, en .NET 4.0, la taille de la pile du thread est de 1Mo.
Le tas (heap)
Le tas est utilisé pour allouer de la mémoire dynamiquement. Il est géré par le Garbage Collector.
La taille du tas dépend de la quantité de mémoire disponible sur l’ordinateur hôte.
Vocabulaire:
Une variable locale est une variable déclarée dans une procédure ou une fonction.
Une variable membre est un membre d'un type; elle est déclarée au niveau module, à l'intérieur d'une classe ou d'une structure , mais pas dans une procédure interne à cette classe ou à cette structure.
4.1 - Type valeur
Les variables de type valeur sont gérées directement sur la pile.
Elles ne nécessitent aucun procédé d’allocation et de libération de mémoire.
Lors de l’entrée dans le code d’une méthode, une zone de stockage est créée dans la pile afin de gérer les types valeur.
Lorsque la méthode n’est plus active, cette zone de stockage est immédiatement libérée.
La taille d’une variable de type valeur est fixe.
L'attribution d'une variable de type valeur à une autre variable de type valeur copie la valeur contenue dans cette variable.
Soit le code suivant :
Dim a, b As Integer
a = 15
b = a
Ci-dessous, le code CIL créé par le compilateur vbc :
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 8 (0x8)
.maxstack 1
.locals init ([0] int32 a,
[1] int32 b)
IL_0000: nop
IL_0001: ldc.i4.s 15
IL_0003: stloc.0
IL_0004: ldloc.0
IL_0005: stloc.1
IL_0006: nop
IL_0007: ret
} // end of method Module1::Main
.locals init ([0] int32 a,
[1] int32 b)
IL_0000: nop
IL_0001: ldc.i4.s 15 place la valeur 15 sur la pile en tant que entier 4 octets (int32)
IL_0003: stloc.0 stocke le contenu de la pile (15) dans la variable locale n°0, et nettoie la pile
IL_0004: ldloc.0 charge le contenu de la variable n°0 (15) et place cette valeur sur la pile
IL_0005: stloc.1 stocke le contenu de la pile (15) dans la variable locale n°1, et nettoie la pile
IL_0006: nop
IL_0007: ret
(Liste des instructions CIL à partir de la page 326 du standard ECMA-335.pdf)
Déclaration d’une variable de type valeur
Pour utiliser une variable de type valeur, on doit déclarer son type avant de pouvoir l’utiliser (voir le chapitre 4.1.1 pour les types autorisés).
On peut aussi lui affecter un modificateur d’accès.
Pour déclarer une variable int32, on utilise la syntaxe suivante :
VB
Dim a As Integer
ou
Dim a As System.Int32
C#
int a;
ou
System.Int32 b;
Un type valeur ne peut pas contenir de valeur null. En cas de besoin, on peut quand même contourner cette limitation en déclarant la variable de type Nullable.
L’utilisation des valeurs null peut etre intéressante lorsque l’on travaille avec des bases de données pour lesquelles cette valeur est courante.
Les types Nullable sont des instances de la structuregénérique System.Nullable<T>.
'VB
Dim variable As Nullable(Of Integer) = Nothing
//C#
Nullable<int> variable = null;
//Ou
int? variable = null;
Il existe deux sortes de type valeur :
-
Les types personnalisés (les structures) qui comprennent :
-
Types intégraux : les entiers de 1 à 8 octets
-
Types virgule flottante : float (4 octets) et double (8 octets)
-
Decimal : réel sur 128 bits (16 octets)
-
Bool : type booléen (True ou False)
-
Structures définies par l'utilisateur.
-
Les énumérations
Tous ces types valeur dérivent (directement ou implicitement) de la classe System.ValueType qui dérive elle‐même de la classe System.Object donnant ainsi accès aux méthodes de ces deux classes.
System.Object -> System.ValueType -> type valeur
Les méthodes les plus utilisées sont :
Equals(Object) : Détermine si l'Object spécifié est égal à l'Object en cours. Cette méthode substitue (remplace) la méthode de la classe de base Object.Equals().
GetType(Object) : Obtient le type de l’instance actuelle
ToString() : Retourne une chaîne qui représente :
-
l'objet actuel (pour un type référence) ex : "MonEspaceDeNoms.MaClasse »
-
ou la valeur contenue dans cet objet (pour un type valeur).
Cette méthode supporte le formatage des données à afficher.
Un type valeur n’est pas dérivable.
4.1.1 - Les types intégrés
Ce sont les types de base par le Framework. On les appelle aussi les types primitifs :
Dans la mesure du possible, il est conseillé d’utiliser les types Int32 et Double car ils sont optimisés par le CLR.
Il faut savoir que le CLR effectue les opérations arithmétiques sur les entiers en utilisant soit des entiers sur 32 bits soit des entiers sur 64 bits.
Ainsi, soit A,B et C 3 variables de type Byte, pour réaliser l’addition A = B + C, les valeurs de ces 2 variables sont d’abord converties dans des entiers sur 32 bits. Elles sont ensuite additionnées. Le résultat est une valeur sur 32 bits qui va être convertie en Byte afin d’être placé dans la variable C.
En VB, cette conversion est implicite.
En C#, cette conversion n’est pas implicite. Il faut donc caster le résultat : A = (Byte) (B + C) ;
4.1.2 - Les énumérations
Les énumérations permettent d’établir des listes de valeurs représentées chacune par un nom unique.
Une énumération est un type valeur qui hérite directement de System.Enum
Un type énumération a un nom, un type sous-jacent qui doit être l'un des types d'entiers signés ou non signés intégrés et un ensemble de champs.
Les types autorisés sont : byte, sbyte, short, ushort, int, uint, long et ulong
Le type par défaut est int.
Les valeurs contenues dans cette liste sont appelées les énumérateurs.
Pour accéder à une de ces valeurs, ont référence l’énumération suivie d’un point et le nom correspondant à la valeur désirée.
Ex : N = <nom de l’énumération>.<nom de l’énumérateur>
La valeur de chaque énumérateur, si elle n’est spécifiée, est automatiquement assignée et sont incrémentées de 1 pour chaque valeur qui se suit :
La première valeur vaut 0, la seconde vaut 1, etc …
Les restrictions suivantes s'appliquent aux énumérations :
Elles ne sont pas dérivables
Elles ne peuvent pas définir leurs propres méthodes.
Elles ne peuvent pas implémenter d'interfaces.
Elles ne peuvent pas définir des propriétés ou des événements.
La liste des valeurs ne peut pas être modifiée par le programme.
Déclaration d’une énumération
En VB :
[ <attributs> ] [modificateur d’accés] [ Shadows ]
Enum Name [ As data type ]
Liste des membres
End Enum
Attributs : facultatif
Un ou plusieurs attributs à appliquer à cette structure
Modificateurs d’accès : facultatif
Une des valeurs suivantes
-
Public
-
Protected
-
Friend
-
Private
-
Protected Friend
Shadows : facultatif
Cette notion sera vue dans le chapitre sur l’occultation.
Enum : obligatoire
Commence la définition de l’énumération
Name : obligatoire
Indique le nom que portera cette énumération. Ce nom sera utilisé en tant que nom de type.
Data Type : facultatif
Type de données de l'énumération et de tous ses membres. Par défaut int.
Liste des membres : obligatoire
Au moins 1 membre
Liste des constantes membres qui sont déclarées dans cette instruction.
Chaque membre emploie la syntaxe et les éléments suivants :
[<attribute list>]member name [ = initializer ]
Attribute list : facultatif
member name : Obligatoire
Nom de ce membre.
Initializer : Facultatif
Expression qui est évaluée au moment de la compilation et assignée à ce membre
End Enum : obligatoire
Termine la définition de l’énumération
Les énumérations permettent d’avoir un code plus compréhensible et plus lisible.
Ex :
Enum MesCouleurs
Rouge
Vert
Bleu
End Enum
Dimc As Integer = MesCouleurs.Bleu ' c vaut maintenant 2
enum MesCouleurs
{
Rouge,
Vert,
Bleu
}
intc = (int) MesCouleurs.Bleu; // c vaut maintenant 2
Dans cet exemple, on n’a pas besoin de se souvenir de la valeur de Bleu qui vaut 2
En C#, il faut toujours faire un cast pour récupérer une des valeurs de l’énumération, même si l’énumération est du même type.
On a vu que l’on peut définir le type qui sera utilisé par les valeurs de l’énumération.
Rappel : Les types autorisés sont : byte, sbyte, short, ushort, int, uint, long et ulong.
Ex :
Enum MesCouleurs as long
Rouge
Vert
Bleu
End Enum
Dimc As long = MesCouleurs.Bleu ' c vaut maintenant 2
enum MesCouleurs : long
{
Rouge,
Vert,
Bleu
}
longc = (long) MesCouleurs.Bleu; // c vaut maintenant 2
On peut aussi se servir des énumérations pour éviter de retenir des valeurs difficiles à mémoriser et pour éviter les risques d’erreur dans le code ou l’on doit s’en servir.
On évite ainsi les erreurs dues à l’utilisation d’une valeur numérique qui ne serait pas permise.
On peut aussi forcer la valeur de chaque énumérateur en donnant des valeurs entières qui ne se suivent pas.
On peut aussi utiliser des valeurs négatives.
La seule condition est d’utiliser une valeur comprise dans l’étendue des valeurs autorisées pour le type sous-jacent.
Ex :
Enum PieceMoteur as long
Bielle = 115984637
Piston = -268751996
Carburateur= 951476354
End Enum
enum PieceMoteur : long
{
Bielle = 115984637,
Piston = -268751996,
Carburateur= 951476354
}
On ne peut pasdéfinir plusieurs énumérateurs avec le même nom mais on peut utiliser la même valeur pour plusieurs énumérateurs.
Ex :
Enum PieceMoteur as long
Bielle = 25
Piston = 25
Carburateur= 30
End Enum
enum PieceMoteur : long
{
Bielle = 25,
Piston = 25,
Carburateur= 30
}
Les énumérations peuvent être aussi utilisées pour représenter des champs de bits.
On ajoute pour cela l’attribut FlagsAttribute() ou Flags() qui indique au CLR de traiter chaque valeur en tant que bit.
Les bits sont ensuite positionnés grace à l’opérateur Or.
VB
<FlagsAttribute()> Enum OptionsFinition As Integer
None = 0
PeintureMetalisee = 1
JantesAlliage = 2
InterieurCuir = 4
GPS = 8
Alarme = 16
EndEnum
Sub Main()
Dim options As OptionsFinition = OptionsFinition.InterieurCuir Or OptionsFinition.JantesAlliage
Console.WriteLine(options.ToString())
End Sub
C#
[Flags]
public enum OptionsFinition
{
None = 0x0,
PeintureMetalisee = 0x1,
JantesAlliage = 0x2,
InterieurCuir = 0x4,
GPS = 0x8,
Alarme = 0x10
}
static void Main()
{
OptionsFinition options = OptionsFinition.InterieurCuir | OptionsFinition.PeintureMetalisee;
Console.WriteLine(options);
}
Le CLR ne fait pas de distinction entre les énumérations traditionnelles et les champs de bits.
C’est le langage qui peut le faire. C’est le cas avec VB et C#
Dans ce cas, les opérateurs binaires peuvent être utilisés sur les champs de bits (mais pas sur les énumérations).
Représentation des enumérations en IL
VB :
Enum mesCouleurs
bleu
vert
rouge
cyan
jaune
violet
EndEnum
Sub Main()
Dim a As Integer = 19
a = mesCouleurs.rouge
a = mesCouleurs.jaune
a = mesCouleurs.violet
End Sub
Dans ILDASM, on peut voir que l’énumération est entièrement définie à partir de valeurs litérales et de constantes.
Ceci est une des raisons pour lesquelles les énumérations ne sont pas modifiables par programmation.
En double-cliquant sur la définition de chacun des membres de l’énumération, on peut observer sa définition :
IL :
.field public static literal valuetype TestsVB.Module1/mesCouleurs bleu = int32(0x00000000)
.field public static literal valuetype TestsVB.Module1/mesCouleurs vert = int32(0x00000001)
.field public static literal valuetype TestsVB.Module1/mesCouleurs jaune = int32(0x00000004)
En consultant le code IL de la méthode Main(), on se rend compte que lors de l’affectation d’une valeur de l’énumération à la variable a, la valeur correspondante est directement placée sur la pile :
.method public static void Main() cil managed
{
.entrypoint
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor() = ( 01 00 00 00 )
// Code size 12 (0xc)
.maxstack 1
.locals init ([0] int32 a)
IL_0000: nop
IL_0001: ldc.i4.s 19
IL_0003: stloc.0
IL_0004: ldc.i4.2
IL_0005: stloc.0
IL_0006: ldc.i4.4
IL_0007: stloc.0
IL_0008: ldc.i4.5
IL_0009: stloc.0
IL_000a: nop
IL_000b: ret
} // end of method Module1::Main
En conclusion :
Les énumérations sont généralement utilisées pour des listes d'éléments uniques, tels que des couleurs, des noms de pays, etc.
Les champs de bits sont généralement utilisés pour des listes dont les valeurs peuvent être combinées entre elles, telles que PeintureMetalisee et InterieurCuir etGPS.
TP
Expliquer l’utilisation de Console.WriteLine ({0}, variable) (situé dans le TP a la fin du chapitre 4.7.5)
Créer une solution TP0412 qui contient un projet console VB TP0412VB et un projet console C# TP0412CS
Effectuer d’abord toutes les manips dans le projet TP0412VB
Puis reproduire le code dans le projet TP0412CS
Travaux :
-
Déclarer des variables de type intégré et faire afficher leur valeur par Console.WriteLine ({0}, variable)
-
Créer une énumération simple au niveau du module (ou de la classe principale en C#). Puis dans Main() créer une variable que l’on charge avec une valeur de cette énumération et afficher cette variable.
-
Créer une énumération champs de bits avec 5 membres. Puis dans Main() créer une variable que l’on charge avec la combinaison de 2 ou 3 des valeurs de l’énumération et afficher le contenu de cette variable.
-
Dans tous les cas, faire afficher les valeurs
4.1.3 - Les types personnalisé ou structures
Un type personnalisé, plus couramment appelé une structure, est un type regroupant plusieurs autres types (valeur ou référence).
Les types personnalisés étant des types valeur, ils sont placés directement sur la pile.
La taille en octets d’un type personnalisé est fixe : c’est la somme de la taille de tous les types qu’il contient.
Les structures sont très utiles pour représenter des valeurs dont les besoins en ressources mémoire sont faibles.
Les structures sont aussi utilisées pour passer des valeurs en tant que paramètres par valeur à des méthodes qui ont des paramètres fortement typés.
Dans la bibliothèque de classes .NET Framework, tous les types de données primitifs (Boolean, Byte, Char, DateTime, Decimal, Double, Int16, Int32, Int64, SByte, Single, UInt16, UInt32 et UInt64) sont définis en interne en tant que structures.
Déclaration d’une structure
En VB :
[ <attributs> ] [ modificateur d’accés ] [ Shadows ] [ Partial ] _
Structure name [ ( Of typelist ) ]
[ Implements interfacenames ]
Declarations des membres
[declarations des méthodes membres ]
End Structure
Attributs : facultatif
Un ou plusieurs attributs à appliquer à cette structure
Modificateurs d’accès : facultatif
Une des valeurs suivantes
-
Public
-
Protected
-
Friend
-
Private
-
Protected Friend
Les structures disposent par défaut d'un accès Friend (Visual Basic).
Shadows : facultatif
Cette notion sera vue dans le chapitre sur l’occultation.
Partial : facultatif
Structure : obligatoire
Commence la définition de la structure
Name : obligatoire
Indique le nom que portera cette structure. Ce nom sera utilisé en tant que nom de type.
Of Typelist : facultatif
Indique qu’il s’agit d’une structure générique. Nous aborderons cette notion avec les classes génériques.
Implements : facultatif
Indique que cette structure implémente les membres d'une ou plusieurs interfaces
Déclarations des membres : obligatoire
Une ou plusieurs instructions Const, Dim, Enum ou Event qui déclarent des données membres de la structure
Déclarations des méthodes membres : facultatif
Déclarations des procédures Function, Operator, Property ou Sub utilisées comme membres de méthode de la structure.
End Structure : obligatoire.
Termine la définition de la structure
Comme les classes, les structures définissent à la fois les données (les champs de la structure) et les opérations qui peuvent être exécutées sur ces données (les méthodes de la structure).
Ex :
Public Structure MaStructure
Public X As Integer
Enum monEnum
rouge
vert
bleu
End Enum
Sub IncX()
X += 1
End Sub
Function AddX(ByVal a As Integer) As Integer
Return X + a
End Function
End Structure
Cela signifie que vous pouvez appeler des méthodes sur des structures, notamment les méthodes héritées des classes System.Object et System.ValueType, ainsi que toute méthode définie sur le type valeur lui-même.
Donc, les structures peuvent comporter des champs, des propriétés et des événements, ainsi que des méthodes statiques et non statiques.
Ex :
Sub Main()
Dim p1 As MaStructure
Dima As Integer = MaStructure.monEnum.bleu
Dim b As Integer = p1.X
P1.X = 15
P1.IncX()
a = p1.X
b = p1.AddX(6)
End Sub
Une structure étant de type valeur, la copie de structure crée donc une copie de l’ensemble des données de la structure.
Ex :
Sub Main()
Dim p1 As MaStructure
P1.X = 15
Dim p2 As MaStructure = p1
P1.X = 30
End Sub
On peut passer les structures en tant que paramètres, les stocker en tant que variables locales ou les stocker dans un champ d'un autre type valeur ou type référence (donc boxing implicite).
Les structures héritent implicitement de System.ValueType, et ne peuvent pas hériter directement de n'importe quel type.
De même, tous les types valeur sont sealed, ce qui signifie qu'aucun autre type ne peut en être dérivé.
De plus, ils ne nécessitent pas de constructeurs.
On peut surcharger les méthodes héritées des classes de base.
Ex :
Structure PointA
Public x, y As Integer
EndStructure
Structure PointB
Public x, y As Integer
Public Overrides Function ToString() As String
ReturnString.Format("x:{0} y:{1}", x, y)
EndFunction
End Structure
Sub Main()
Dim pt1 As PointA
Dim pt2 As PointB
Console.WriteLine(pt1.ToString())
Console.WriteLine(pt2.ToString())
Console.Read()
End Sub
Pt1.ToString() affiche ConsoleApplication.Module1+PointA
Pt2.ToString() affiche x :0 y :0
On peut implémenter l'opérateur new pour créer un objet structure : dans ce cas, la structure est créée et le constructeur approprié est appelé.
Contrairement aux classes, les objets structure peuvent être instanciés sans recours à l'opérateur new.
Dans un tel cas, il n'y a aucun appel de constructeur, ce qui rend l'allocation plus efficace.
Toutefois, les champs ne seront pas assignés et l'objet ne pourra pas être utilisé tant que tous les champs n'auront pas été initialisés.
Seuls les champs statiques peuvent être initialisés dans la définition de la structure.
Lorsqu'une structure contient un type référence en tant que membre, le constructeur par défaut du membre doit être appelé explicitement, sinon le membre reste non assigné et la structure ne peut pas être utilisée.
Ex :
PublicClass Class2
Public Age
Sub New()
Age = 18
End Sub
EndClass
StructurePointB
Public x, y As Integer
Shared Z As Integer = 3 ‘ Z peut etre initialisé car il est statique (shared)
Publicc2 As Class2
End Structure
Sub Main()
Dim pt2 As New PointB
pt2.c2 = New Class2
End Sub
TP
Créer une solution TP0413 qui contient un projet console VB TP0413VB et un projet console C# TP0413CS
Effectuer d’abord toutes les manips dans le projet TP0413VB
Puis reproduire le code dans le projet TP0413CS
Travaux :
-
Créer une structure qui contient 1 entier, 1 double et un boolean.
-
Dans Main(), créer 1 variable S1 représentant cette structure. Créer une 2eme variable S2 par copie de S1.
-
Modifier les valeurs de S1. Et faire afficher le contenu des valeurs de S1 et de S2.
-
Créer un second fichier module Module2 d’accès public et y placer à l’intérieur une nouvelle structure avec quelques champs.
-
Depuis la fonction main() de Module1, tester l’accès à la nouvelle structure de Module2 et à ses champs lorsque l’on change les modificateurs d’accès de cette structure.
4.2 - Type référence
Les variables de type Référence sont des pointeurs placés sur la pile et dont la valeur est placée dans le tas.
Les variables de type référence fonctionnent de la même façon que les pointeurs dans le langage C : La variable ne contient pas directement une valeur mais une adresse en mémoire qui indique à quel endroit de la mémoire sont stockées les données.
Les types String, Object, Class, Interface, les tableaux, … sont des types référence.
Lorsqu’une variable de type référence est déclarée, le CLR crée une variable qui pourra contenir un pointeur vers l’objet correspondant.
VB :
Dimcl1 As MaClasse
IL :
.locals init ([0] class MonProjetVB.MaClasse cl1)
Lorsque l’objet est instancié par l’appel au mot clé New, la mémoire est allouée dans le heap managé et un pointeur sur cette adresse est renvoyée sur la pile d’exécution, puis placé dans la variable Cl1 :
VB :
Cl1 = New MaClasse
IL :
IL_0000: nop
IL_0001: newobj instance void MonProjetVB.MaClasse::.ctor()
IL_0002: stloc.1
La mémoire occupée par l’objet correspond à la somme des éléments suivants :
la taille du type de l’objet,
+ un pointeur de tableau de méthodes ,
+ un SyncBlockIndex (utilisé entre autres pour les verrouillages d’objet).
La copie d’une variable de type référence ne copie pas le contenu de cette variable, mais seulement le pointeur.
Soit Objet1 l’instance d’un type référence,
L’instruction Objet2 = Objet1 va avoir pour résultat de copier l’adresse mémoire contenue dans Objet1 dans Objet2.
Objet1 et Objet2 pointeront vers la même adresse mémoire située sur le Tas.
ATTENTION : le type String apporte une exception à la règle.
L’opérateur d’affectation = a été redéfini afin de fonctionner comme avec les types valeurs.
C’est-à-dire que la copie d’une variable de type String dans une autre variable de type String va copier la valeur de la String et non pas le pointeur comme pour tous les autres types par référence.
Le type String correspond en fait à la classe System.String du .NET, ce qui permet d’utiliser les nombreuses fonctions de traitement de chaines de caracteres présentes dans cette classe.
Le type String représente une séquence de caractères unicode (sur 2 octets chacun) qui peut contenir 2^31 caractères unicode (soit un peu plus de 2 milliards, soit environ 2 Go).
4.3 – Gestion en mémoire des types référence
Le Garbage Collector (également appelé ramasse-miettes) du .NET Framework manage l'allocation et la libération de mémoire dans votre application.
Chaque fois que vous créez un objet, le CLR alloue de la mémoire à l'objet à partir du tas managé.
Tant que de l'espace d'adressage est disponible dans le tas managé, le runtime continue à allouer de l'espace pour les nouveaux objets.
Toutefois, la taille de la mémoire utilisable n'est pas infinie.
Le garbage collector doit finir par effectuer un garbage collection afin de libérer de la mémoire.
Le moteur d'optimisation du garbage collector détermine le meilleur moment pour effectuer un garbage collection en fonction des allocations en cours.
Lorsque le garbage collector effectue un garbage collection, il recherche les objets figurant dans le tas managé qui ne sont plus utilisés par l'application et effectue les opérations nécessaires pour récupérer leur mémoire.
Tous les processus sur le même ordinateur partagent la même mémoire physique et le fichier d'échange s'il en existe un, mais chaque processus possède son propre espace d'adressage virtuel séparé.
Par défaut, sur les ordinateurs 32 bits, chaque processus a un espace d'adressage virtuel de 2 Go.
Il existe donc un tas managé pour chaque processus managé. Tous les threads d’un processus allouent des objets sur le même tas.
Allocation de la mémoire : principe des étagères
Le GAC gère 3 générations pour le tas managé, que l’on peut assimiler à des étagères de longueurs différentes.
Dès qu’un objet est instancié, il est placé sur l’étagère 0 (petite taille).
Quand l’étagère 0 est pleine, le GAC déplace les objets vers l’étagère 1 (plus grande). Si cette étagère 1 est pleine, il déplace des objets de l’étagère 1 vers la 2 (très grande taille).
Quand l’étagère 2 est pleine : Out Of Memory !
Ces étagères sont appelées Génération au sens du Garbage Collector.
Génération 0.
Il s'agit de la génération la plus jeune, qui contient des objets éphémères. Un exemple d'objet éphémère est une variable temporaire. Le garbage collection a le plus souvent lieu dans cette génération.
Les objets alloués récemment forment une nouvelle génération d'objets et sont implicitement des collections de génération 0, à moins qu'ils ne s'agisse de grands objets, auquel cas ils entrent dans le tas d'objets volumineux dans une collection de génération 2.
La plupart des objets sont libérés lors de l'opération garbage collection dans la génération 0 et ne survivent pas à la génération suivante.
Génération 1.
Cette génération contient des objets éphémères et sert de tampon entre objets éphémères et objets durables.
Génération 2.
Cette génération contient des objets durables. Un exemple d'objet durable est un objet dans une application serveur qui contient des données statiques qui sont vivantes pour la durée du processus.
Libération de la mémoire : le garbage collection
Le garbage collection se produit lorsque l'une des conditions suivantes est vraie :
Le système possède peu de mémoire physique.
La mémoire utilisée par les objets alloués sur le tas managé dépasse un seuil acceptable. Cela signifie qu'un seuil d'utilisation de la mémoire acceptable a été dépassé sur le tas managé.
La méthode GC.Collect est appelée.
Dans presque tous les cas, vous n'avez pas à appeler cette méthode, car le garbage collector s'exécute continuellement. Cette méthode est principalement utilisée pour les situations uniques et les tests.
Lorsqu'un objet finalisable (disposant d’un destructeur) est détecté comme étant mort, son finaliseur est placé dans une file d'attente afin que ses actions de nettoyage soient exécutées, mais l'objet lui-même est promu à la génération suivante.
Par conséquent, vous devez attendre jusqu'au garbage collection suivant sur cette génération (qui n'est pas nécessairement le garbage collection suivant) pour déterminer si la mémoire occupée par l'objet a été récupérée.
D’une manière générale, il vaut mieux laisser le Garbage collector s’occuper tout seul de la libération de la mémoire car il est optimisé pour cela.
4.4 - Conversions de type
Il existe deux types de conversions :
Narrowing: c’est l’opération qui consiste à convertir un type d'un domaine étendu en un type possédant un domaine plus restreint (exemple, Double vers Int). Cette conversion a de fortes chances d'entraîner des pertes de données.
Widening: c’est l’opération qui consiste à convertir un type d'un domaine restreint vers un type ayant un domaine plus étendu (Exemple, Int vers Double). Cette conversion ne peut pas perdre de données.
En Visual Basic, les conversions Narrowing et Widening sont implicites par défauts.
En C#, les conversions Widening sont implicites alors que les conversions Narrowing doivent obligatoirement être explicitées !
Note : Il est possible de configurer Visual Basic afin qu’il ait le même comportement que le C# face aux conversions. Pour ceci, allez dans les propriétés du projet, sélectionnez l’onglet Compiler. Puis passez le mode "Option Strict" sur On.
Le .NET Framework fournit quelques outils pour effectuer ces conversions.
Cependant, VB.NET et C# possèdent chacun des spécificités qui leur sont propres:
VB.NET
|
C#
|
System.convert
|
System.convert
|
CType
|
Opérateur (type) pour effectuer un cast.
|
CBool, CInt, Cstr …
|
|
DirectCast
TryCast
|
|
System.Convert : VB et C#
Ce type expose des méthodes qui permettent de convertir un type de données de base en un autre type de données de base.
Pour chaque type de base, il existe une méthode pour convertir ce type dans les autres types de base.
Ex : ToByte(char), ToByte(Double), ToByte(Int16), …
ToDouble(Byte), ToDouble(Int16), ToByte(Object), …
ToInt16(Byte), ToInt16(Double), ToInt16(Object), …
Les opérations de conversions peuvent lever les exceptions suivantes :
OverflowException : dépassement de capacité (rencontré en narrowing)
Ex : conversion d'un Int32dont la valeur est 10 000 en un type Byte
FormatException : le paramètre attendu n’est pas du bon type
Ex : Une chaîne à convertir en valeur Char est constituée de plusieurs caractères
InvalidCastException : méthode de conversion non prise en charge
Ex : Conversions de Boolean, Single, Double, Decimal ou DateTime en Char
Certainesde ces méthodes lèvent toujours une exception, exemple :
ToByte(DateTime), ToChar(Boolean), ToInt16(DateTime), …
Pour convertir des objets, ceux-ci doivent implémenter l'interface System.IConvertible
Ex :
VB
Dim a As Long = Long.MaxValue
Dim b As Integer = System.Convert.ToInt16(a)
C#
Int64 a = Int64.MaxValue;
int b = System.Convert.ToInt32(a);
CBool, CInt, Cstr … : VB
Même utilisation que system.convert, mais offre de meilleures performances en VB.NET pour la conversion des types de bases.
Ex :
Dim a As Long = 3250
Dimb As Integer = CInt(a)
Ctype (expression à convertir, typename) : VB
Convertit explicitement toute expression en tout type de données, objet, structure, classe ou interface
CType peut lever les exceptions suivantes :
OverflowException : dépassement de capacité (rencontré en narrowing)
Ex : conversion d'un Int32dont la valeur est 10 000 en un type Byte
InvalidCastException : methode de conversion non prise en charge
Ex : Conversions de Boolean, Single, Double, Decimal ou DateTime en Char
Ex :
VB
Dim a As Integer = 15245
Dim c As Long = CType(a, Integer)
Dim s1 As maStructure
Dim s2 As Object = CType(s1, Object)
Dim s3 As maStructure = CType(s2, maStructure)
Cast : C#
Convertit explicitement toute expression en tout type de données, objet, structure, classe ou interface
C’est l’équivalent de CType du VB
Le Casting peut lever les exceptions suivantes :
OverflowException : dépassement de capacité (rencontré en narrowing)
Ex : conversion d'un Int32dont la valeur est 10 000 en un type Byte
InvalidCastException : methode de conversion non prise en charge
Ex : Conversions de Boolean, Single, Double, Decimal ou DateTime en Char
Ex :
C#
int a = 15245;
long c = (long)a;
maStructure s1 = new maStructure();
object s2 = (object)s1;
maStructure s3 = (maStructure)s2;
DirectCast et TryCast : VB
Ces fonctions permettent la conversion de type basée sur l'héritage ou l'implémentation.
DirectCast requiert un héritage ou une relation d'implémentation entre les types de données des deux arguments.
Cela signifie qu'un type doit hériter de l'autre ou l'implémenter.
Semblable à CType mais utilisé car il donne parfois de meilleures performances lors de la conversion vers et à partir du type de données Object.
A la différence des autres méthodes de conversion, TryCast ne lève pas d’exceptions mais renvoie True si la conversion a été correctement réalisée, sinon False.
On reverra ces 2 méthodes après avoir étudié les classes et l’héritage.
4.5 – L’inférence de type
L'inférence est la capacité de déduire le type d'une variable par analyse du type fourni en entrée.
C'est donc le compilateur qui déduit le type de la variable.
VB
Dim str1 As String = "Rouge" ' Typé explicitement en String
Dim a As Long = 250 ' Typé explicitement en Long
Dim c As Boolean = True ' Typé explicitement en Boolean
Dim e As Byte = 65 ' Typé explicitement en Byte
Dim str2 = "Vert" ' l'inférence de type a typé la variable en tant que String
Dim b = (System.Int64).MaxValue ' l'inférence de type a typé la variable en tant que Long
Dim d = True ' l'inférence de type a typé la variable en tant que Boolean
Dim f = 65 ' l'inférence de type a typé la variable en tant que Integer et pas en Byte
Dim cl1 As New c1 ' Typé explicitement en type MaClasse
Dim cl2 = cl1 ' l'inférence de type a typé la variable en tant que type MaClasse
Code IL produit par les 6 dernières lignes ci-dessus (le code VB a été rajouté pour la compréhensibilité du code IL) :
.method public static void TestInference1() cil managed
{
// Code size 34 (0x22)
.maxstack 1
.locals init ([0] int64 b,
[1] class _1._5._2_ProjetVB.Vache cl1,
[2] class _1._5._2_ProjetVB.Vache cl2,
[3] bool d,
[4] int32 f,
[5] string str2)
IL_0000: nop
Dimstr2 = "Vert"
IL_0001: ldstr "Vert"
IL_0006: stloc.s str2
Dimb = (System.Int64).MaxValue
IL_0008: ldc.i8 0x7fffffffffffffff
IL_0011: stloc.0
Dimd = True
IL_0012: ldc.i4.1
IL_0013: stloc.3
Dimf = 65
IL_0014: ldc.i4.s 65
IL_0016: stloc.s f
Dimcl1 As New c1
IL_0018: newobj instance void _1._5._2_ProjetVB.Vache::.ctor()
IL_001d: stloc.1
Dimcl2 = cl1
IL_001e: ldloc.1
IL_001f: stloc.2
EndSub
IL_0020: nop
IL_0021: ret
} // end of method Mod_TestInference::TestInference1
Si le compilateur n’arrive pas à déterminer le type d’un variable, il lui donne le type Object.
En C#, L’inférence de type est mise en œuvre au travers du mot clé var.
C#
string str1 = "Rouge"; // Typé explicitement en String
var str2 = "Vert"; // l'inférence de type a typé la variable en tant que String
long a = 250; // Typé explicitement en Long
var b = a; // l'inférence de type a typé la variable en tant que Long
bool c = true; // Typé explicitement en Boolean
var d = true; // l'inférence de type a typé la variable en tant que Boolean
byte e = 65; // Typé explicitement en Byte
var f = 65; // l'inférence de type a typé la variable en tant que Integer et pas en Byte
var g = e; // l'inférence de type a typé la variable en tant que Byte car e est typé
MaClasse cl1 = new MaClasse();
var cl2 = cl1;
4.6 – Boxing et Unboxing
On appelle boxing (ou compartimentation ou encore emboitage) toute opération qui consiste à transformer un type valeur en un type référence.
On appelle unboxing (ou décompartimentation) l'opération inverse, c'est à dire, toute opération qui consiste à transformer un type référence en un type valeur.
Une variable qui a subi une opération boxing est dite boxed ou compartimentée.
Une variable qui a subi une opération unboxing est dite unboxed ou décompartimentée.
Par exemple, un entier est un type valeur. Placer cet entier dans un type Object signifie faire une opération boxing. En effet, Object est un type référence. Il doit bien il y avoir conversion d'un type valeur (notre entier) vers un type référence (notre objet).
Lors de l'opération boxing, la variable de type valeur est mise dans une "boîte". Sur cette boîte est déposée une étiquette indiquant le type complet du contenu de la boîte. Par exemple, pour notre entier, la boîte aura pour label : System.Int32.
Pour que l'opération unboxing puisse avoir lieu, il faut que le programme demande à retirer de l'objet le type exact qu'il contient. C'est à dire que si notre boîte a pour étiquette System.Int32, il sera impossible d'en retirer un type long (System.Int64) car ces types sont différents et l'opération sera interdite (bien qu'un type long puisse contenir un type int32).
D'une manière générale, en VB.NET, vous aurez très peu à vous soucier de où et quand se produisent des boxing ou unboxing car elles sont la plupart du temps transparentes pour le programmeur qui y voit plutôt un transtypage.
En C#, la méthode suivante provoque une erreur lors de l’exécution de l’application:
La méthode correcte serait :
En VB, les mêmes lignes de code ne produisent pas le même code IL selon la valeur du commutateur Option Strict.
En plaçant le commutateur Option Strict sur On (on place la ligne suivante au début du fichier qui contient la fonction) :
La compilation du code VB produit une erreur lors de la compilation : Option Strict On interdit les conversions implicites de Object vers Short.
Maintenant, en plaçant le commutateur Option Strict sur Off (on place la ligne suivante au début du fichier qui contient la fonction) :
La compilation du code VB ne produit plus aucune erreur, mais on se rend compte que le code IL n’est plus le même.
L’appel à l’unboxing a été remplacé par un appel à une fonction de conversion qui attend en paramètre un object et qui renvoie la valeur directement dans le type attendu, ici un short.
VB:
Dim x As Integer = 7
Dimo As Object = x
Dim y As Int16 = o
IL :
.method public static void TestBoxing1() cil managed
{
// Code size 19 (0x13)
.maxstack 1
.locals init ([0] object o,
[1] int32 x,
[2] int16 y)
IL_0000: nop
IL_0001: ldc.i4.7
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call int16 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToShort(object)
IL_0010: stloc.2
IL_0011: nop
IL_0012: ret
} // end of method Mod_TestInference::TestBoxing1
La méthode correcte en VB avec Option Strict On serait la suivante :
VB:avec Option Strict On
Dim x As Integer = 7
Dim o As Object = x
Dim y As Int16 = CShort(CInt(o))
IL :
.method public static void TestBoxing2() cil managed
{
// Code size 20 (0x14)
.maxstack 1
.locals init ([0] object o,
[1] int32 x,
[2] int16 y)
IL_0000: nop
IL_0001: ldc.i4.7
IL_0002: stloc.1
IL_0003: ldloc.1
IL_0004: box [mscorlib]System.Int32
IL_0009: stloc.0
IL_000a: ldloc.0
IL_000b: call int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToInteger(object)
IL_0010: conv.ovf.i2
IL_0011: stloc.2
IL_0012: nop
IL_0013: ret
} // end of method Mod_TestInference::TestBoxing2
Bien que le commutateur Option Strict Off simplifie le développement en créant des conversions implicites, il est quand même plus propre de développer en respectant les principes de typage.
Attention, Il ne faut pas abuser des boxing et unboxing car les ressources mises en œuvre font diminuer les performances.
Par exemple, préférez l'usage de la classe List à celui de ArrayList car ArrayList ne prend que des types object en paramètre dans sa méthode Add alors que List prend le type exact de donnée attendu. Nous verrons cela en détail dans le chapitre sur les collections.
4.7 – Portée et modificateurs d’accès
Les modificateurs d’accès permettent de déterminer depuis quel emplacement du code un élément sera accessible. Cela s’appelle aussi la portée.
Il existe 5 modificateurs d’accès.
-
Public
-
Friend ou internal
-
Protected
-
ProtectedFriend
-
Private
4.7.1 - Public
Le mot clé Public spécifié dans la déclaration d’un élément indique que cet élément sera accessible depuis n’importe quel autre élément du projet ainsi que depuis tout assembly externe qui fait référence à ce projet.
On peut définir un élément Public au niveau d’un fichier, d’un module, d’une classe, d’une structure ou d’une interface, mais pas au niveau d’une fonction ou d’une procédure.
VB
PublicClass Voiture
Public Marque As String
EndClass
C#
publicclass Voiture
{
public string Marque;
}
4.7.2 – Friend (VB) ou internal (C#)
Le mot clé Friend spécifié dans la déclaration d’un élément indique que cet élément sera accessible uniquement par le code contenu dans le même assembly.
On peut définir un élément Friend au niveau d’un fichier, d’un module, d’une classe, d’une structure ou d’une interface, mais pas au niveau d’une fonction ou d’une procédure.
VB
PublicClass BonRestaurant
Friend Nom As String
Friend Adresse As String
Public Menu As String
EndClass
C#
publicclass BonRestaurant
{
internal string Nom;
internal string Adresse;
publicstring Menu;
}
4.7.3 - Protected
Le membre n’est accessible que par le code contenu dans la même classe ou la même structure, ou dans les classes dérivées de cette classe.
On peut définir un élément Protected uniquement au niveau d’une classe et uniquement pour déclarer un membre de cette classe.
On ne peut donc pas utiliser Protected au niveau d’un fichier, d’un espace de noms, d’un module, d’une interface, d’une structure, d’une fonction ou d’une procédure car ces éléments ne prennent pas en charge l’héritage.
VB
PublicClass Logement
Public Adresse As String
Protected CodeAcces As String
EndClass
C#
publicclass Logement
{
public string Adresse;
protected string CodeAcces;
}
4.7.4 - Protected Friend (VB) ou protected internal (C#)
Ce modificateur est une combinaison des 2 modificateurs Protected et Friend (ou internal).
Le membre n’est accessible que par les classes dérivées et les éléments contenus dans le même assembly.
On peut définir un élément Protected Friend uniquement au niveau d’une classe et uniquement pour déclarer un membre de cette classe.
On ne peut donc pas utiliser Protected au niveau d’un fichier, d’un espace de noms, d’un module, d’une interface, d’une structure, d’une fonction ou d’une procédure car ces éléments ne prennent pas en charge l’héritage.
4.7.5 - Private
Le membre n’est accessible que par le code contenu dans le même module, la même classe ou la même structure.
On ne peut définir un élément Private qu’au niveau d’un module.
On peut donc déclarer un élément Private à l’intérieur d’un module, d’une classe ou d’une structure, mais pas au niveau d’un fichier ou d’un espace de noms, ni dans une interface, une fonction ou une procédure.
Au niveau du module, l’instruction Dim équivaut à l’utilisation de Private.
VB
PublicClass Personne
Public Nom As String
Private CodeCB As String
EndClass
C#
publicclass Personne
{
public string Nom;
private string CodeCB;
}
Par défaut, si aucun modificateur n’est spécifié, c’est l’accés Private qui est utilisé.
C’est toujours la portée de l’élément supérieur qui l’emporte.
Si on déclare une classe Private et qu’on lui ajoute une propriété Public, cette propriété ne sera jamais atteignable en dehors de la portée de la classe.
TP
Rappel sur l’utilisation de Console.WriteLine() pour afficher des valeurs sur la console
Dim a As Integer = 213
Dim s As String = "Chaine de caracteres"
Dim d As Date = Now
Dim r As Double = 1.523875546
Console.WriteLine("a= {0} et b= {1} et c={2}", a, s, d)
a= 213 et b= Chaine de caracteres et c=08/12/2011 22:40:07
Console.WriteLine(""a= {0,-10} et b= {1} et c={2:d}", a, s, d)
a= 213 et b= Chaine de caracteres et c=08/12/2011
Console.WriteLine(""a= {0,10} et b= {1} et c={2:d}", a, s, d)
a= 213 et b= Chaine de caracteres et c=08/12/2011
Console.WriteLine(""a= {0,10:X} et b= {1} et c={2:d}", a, s, d)
a= D5 et b= Chaine de caracteres et c=08/12/2011
Console.WriteLine(""a= {0,10:X} et b= {1} et c={2:t}", a, s, d)
a= D5 et b= Chaine de caracteres et c=22:48
Console.WriteLine("r= {0}", r)
R=1.523875546
Console.WriteLine("r= {0:0.00}", r)
R=1.52
Créer une solution TP044 qui contient un projet console VB TP044VB et un projet console C# TP044CS
Effectuer d’abord toutes les manips dans le projet TP044VB
Puis reproduire le code dans le projet TP044CS
Travaux :
-
Créer une procédure TestConvert() pour tester les conversions de type avec CType() , system.convert et CInt
-
Créer une procédure TestInfer() avec 4 ou 5 lignes de code utilisant l’inférence de type
-
Créer une procédure TestBoxing() avec au moins une opération de boxing et une opération de unboxing.
Pour chacune de ces procédures, faire afficher les résultats avec Console.Writeline()