I. Public concerné▲
Testé sous le framework .NET 1.1 et Delphi 2005.
Version 1.0
Concernant l'EDI de Delphi 2005 vous pouvez consulter l'article de SJRD : Prise en main de Delphi 2005.
Cet article contient des liens nommés Local. Ils pointent sur des URL locales de la documentation de Delphi 2005 et de Microsoft. Leur utilisation nécessitera l'installation du SDK .NET v1.1 en français sur le poste.
Ils sont uniquement accessibles Offline, les liens 'ms-help' exécutent en local l'aide en ligne du SDK.
I-A. Les sources▲
Les fichiers sources des différents exemples :
FTP.
HTTP.
L'article au format .PDF.
II. Aperçu de la plate-forme .NET▲
Extrait du SDK :
"Le .NET Framework est une nouvelle plate-forme informatique qui simplifie le développement d'applications dans l'environnement fortement distribué d'Internet. Le .NET Framework est conçu pour remplir les objectifs suivants :
- Fournir un environnement cohérent de programmation orientée objet que le code objet soit stocké et exécuté localement, exécuté localement, mais distribué sur Internet ou exécuté à distance.
- Fournir un environnement d'exécution de code qui minimise le déploiement de logiciel et de conflits de versioning.
- Fournir un environnement d'exécution de code qui garantit l'exécution sécurisée de code y compris le code créé par un tiers d'un niveau de confiance moyen ou un tiers inconnu.
- Fournir un environnement d'exécution de code qui élimine les problèmes de performance des environnements interprétés ou écrits en scripts.
- Fournir au développeur un environnement cohérent entre une grande variété de types d'applications comme les applications Windows et les applications Web.
- Générer toutes les communications à partir des normes d'industries pour s'assurer que le code basé sur le .NET Framework peut s'intégrer à n'importe quel autre code.
Le .NET Framework contient deux composants principaux : le Common Language Runtime et la bibliothèque de classes du .NET Framework. Le Common Language Runtime est la base du .Net Framework. Le runtime peut être considéré comme un agent qui manage le code au moment de l'exécution, fournit des services essentiels comme la gestion de la mémoire, la gestion des threads, et l'accès distant. Il applique également une stricte sécurité des types et d'autres formes d'exactitude du code qui garantissent un code sécurisé et robuste. En fait, le concept de gestion de code est un principe fondamental du runtime. Le code qui cible le runtime porte le nom de code managé, tandis que le code qui ne cible pas le runtime porte le nom de code non managé. La bibliothèque de classes, l'autre composant principal du .NET Framework, est une collection complète orientée objet, de types réutilisables que vous pouvez utiliser pour développer des applications allant des traditionnelles applications à ligne de commande ou à interface graphique utilisateur (GUI, Graphical User Interface) jusqu'à des applications qui exploitent les dernières innovations fournies par ASP.NET, comme les services Web XML et Web Forms.
…"
Lire la suite. Local.
II-A. Quelques détails sur le framework .NET▲
Le Runtime
Il se situe dans le répertoire C:\WINDOWS\Microsoft.NET\Framework.
Le SDK
Il se situe dans C:\Program Files\Microsoft.NET\SDK\v1.1\, n'hésitez pas à le parcourir il contient évidemment de nombreux outils, sources et informations diverses.
Le GLobal Assembly Cache (GAC)
Le GAC remplace selon Microsoft la registry pour la centralisation des informations d'assemblage de .NET.
Il se situe dans le répertoire %Windir%\assembly sa visualisation dans l'exploreur se fait d'une manière différente des autres répertoires, ici l'exploreur charge la DLL Shfusion.dll qui est une extension du shell Windows (local) permettant de visualiser son contenu. Bien évidemment il est possible de le visualiser en 'mode fichier' dans une console Windows.
Attention la manipulation du GAC doit se faire au travers d'outils appropriés, voir le cache de l'assembly global. Local.
À noter que certaines clés de registre référencent des entrées du GAC :
HKEY_CLASSES_ROOT\Installer\Assemblies et HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Installer\Assemblies.
Vous trouverez dans cette clé HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework le répertoire d'installation du SDK et du Runtime
Recopiez, dans le répertoire contenant le SDK .NET, le script « …\Microsoft.NET\SDK\v1.1\Bin\sdkvars.bat » dans le répertoire C:\Windows. Vous pourrez ainsi déclarer les variables d'environnement selon les besoins.
III. Introduction▲
De nouvelles fonctionnalités ont été ajoutées au langage Delphi pour .NET afin de supporter des concepts et des fonctionnalités de programmation propre à la plate-forme .NET, et ce tout en respectant la CLS (Common Language Specification).
Cette adaptation pouvant laisser croire qu'il s'agit d'un nouveau langage alors qu'en fait ce qui est nouveau c'est la plate-forme .NET, c'est une évidence qu'il est bon de rappeler, votre premier objectif étant d'abandonner certaines pratiques et habitudes liées à la programmation sous Win32, qui à mon sens sont un frein à la compréhension de .NET. N'essayez donc pas de faire du WIN32 sous .NET, mais ceci est plus facile à dire qu'a faire ;-).
Selon moi, un des points importants est qu'étant donné que sous .NET tout est objet vous devez 'penser' objet, plus encore que sous Win32.
Bien évidemment les problématiques telles que la gestion des threads ne changent pas ( .NET s'appuyant sur WIN32 ), seule leur implémentation diffère.
J'aborderais ici ces nouveautés du langage ainsi que les différences de comportement les plus sensibles. Ces quelques informations permettront aux débutants sous Delphi pour .NET, d'appréhender au mieux les bases de .NET et du langage Delphi pour .NET. Pour simplifier, j'ai principalement utilisé des applications console dans les exemples proposés.
Cette introduction n'aborde pas les problématiques de migration de code Win32 vers .NET, consultez à ce sujet l'ouvrage d'Olivier Dahan ou les articles appropriés sur le site de Borland US.
De nombreuses informations issues du SDK .NET émailleront cet article, autant puiser à la source.
IV. Les pointeurs n'existent plus sous DotNet ?▲
Sous Dot Net la manipulation d'adresse n'est plus possible dans du code managé étant donné que l'organisation de la mémoire est désormais du domaine exclusif du Garbage Collector (ramasse-miettes) et plus du programmeur.
Cette prise en charge de la mémoire par le Garbage Collector implique une réorganisation périodique de la mémoire, on ne peut donc plus avoir de référence fixe sur des zones mémoire.
La manipulation de pointeur reste possible, mais dans ce cas votre code est considéré comme unsafe, non sécurisé. Ceci pouvant avoir comme conséquence de limiter ou d'interdire l'intégration de ce code dans d'autres applications .NET.
Voir aussi
Garbage Collector : Le GC est un sous-ensemble de la CLR. Local.
Le Common Language Runtime (CLR) gère la mémoire, l'exécution des threads, l'exécution du code, la vérification de la sécurité du code, la compilation et d'autres services du système. Ces fonctionnalités font partie intégrante du code managé qui s'exécute sous le Common Language Runtime. Local.
L'article sur les pointeurs sous .NET par Nono40.
Certains liens peuvent ne pas s'afficher directement, dans ce cas valider à nouveau l'URL dans votre browser.
V. Type primitif▲
Les types primitifs sous .NET sont Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Char, Double et Single.
Ils correspondent directement aux types qui existent dans le Framework .NET
Type Delphi |
Mappage de types .NET |
---|---|
Boolean |
Boolean |
Shortint |
SByte |
Byte |
Byte |
Smallint |
Int16 |
Word |
UInt16 |
Longint |
Int32 |
Integer |
Int32 |
Longword |
UInt32 |
Int64 |
Int64 |
Cardinal |
UInt32 |
Char |
Char |
Single |
Single |
Double |
Double |
VI. Value Types et Reference Types▲
Les allocations de blocs de données, par exemple, se font désormais soit dans la pile soit sur le tas (Managed Heap). Si on ne manipule plus d'adresse comment dans ce cas passer en paramètre un objet sans avoir à recopier l'intégralité de son contenu sur la pile ?
Sous .Net les données sont de deux types :
- Type valeur
Les types valeur contiennent directement leurs données; les instances de types valeur sont allouées sur la pile. - Type référence
Les types référence stockent une référence à l'adresse mémoire de la valeur et sont alloués sur le tas managé.
Je tiens à remercier Emerica qui m'a proposé ce graphique, m'évitant ainsi des explications des plus hasardeuses sur le sujet et confirmant par là même qu'un dessin vaut mieux qu'un long discours.
Les variables de type valeur stockent des données alors que les variables de type référence stockent les références aux données. Les types référence sont également considérés comme des objets comme on peut le voir sur le schéma précédent.
On voit donc que la notion d'indirection persiste sous .Net, mais n'a plus rien à voir avec les pointeurs présent sous Win32, Win16 ou MS-DOS. La possibilité d'interpréter des données pointées, c'est-à-dire la possibilité de transtyper des données, est également affectée. On retrouve donc la notion de typage fort, chère au Pascalien.
Il faut savoir que sous .NET tout est objet. La classe System.Object prend en charge toutes les classes de la hiérarchie des classes du .NET Framework et fournit des services de bas niveau aux classes dérivées. Il s'agit de la classe de base fondamentale parmi toutes les classes du .NET Framework. Elle constitue la racine de la hiérarchie des types.
Voir Aussi
Vue d'ensemble du système de type commun. Local.
Qu'est-ce que la spécification CLS (Common Language Specification) ? Local.
VI-A. Principales fonctionnalités des types valeur▲
- Une variable d'un type valeur contient toujours une valeur de ce type.
- L'assignation d'un type valeur à une variable entraîne la création d'une copie de la valeur assignée alors que l'assignation d'un type référence à une variable crée une copie de la référence, mais pas de l'objet référencé.
- Tous les types valeur sont dérivés implicitement de la classe .NET System.Object.
- Les variables d'un type valeur sont généralement des types primitifs et assignées sur la pile.
- Contrairement aux types référence, il n'est pas possible de dériver un nouveau type à partir d'un type valeur existant. En revanche, comme les types référence, les types Record peuvent implémenter des interfaces.
- Un type valeur ne peut pas contenir de valeur null/NIL, à la différence d'un type référence.
- Chaque type valeur possède implicitement un constructeur par défaut qui initialise la valeur par défaut du type associé.
- Puisqu'un type de valeur contient une copie de ses données, la modification d'un type de valeur modifiera seulement cet exemple particulier.
- Cependant, la modification d'un type de référence modifiera toutes les instances qui ont une référence à ce type.
Si vous souhaitez créer un nouveau type par valeur, vous devez le dériver de System.ValueType, c'est-à-dire à partir d'un Record sous Delphi .NET. Les classes étant utilisées pour créer de nouveau type par référence.
VI-B. Un peu de pratique▲
Projet : ..\TypeValue-Ref\ValeurReference
Nous aborderons plus avant le détail des nouvelles fonctionnalités associées au type de donnée Record.
Chargez le projet et regardons ici la modification d'un type valeur :
Type
monRecord=Record
À :Integer
;
B :Integer
;
Constructor
Create(p1,p2:integer
);
end
;
Constructor
monRecord.Create(p1,p2:integer
);
begin
A:=p1;
B:=p2;
end
;
var
RInstance1, RInstance2 : monRecord;
Begin
RInstance1:= monRecord.Create(5
, 3
) ; // Création d'un objet.
// L'assignation directe des champs par RInstance1.A 9 est possible.
RInstance2:= RInstance1; // Recopie de l'objet RInstance1 dans RInstance2
RInstance1.A:= 7
; // Le champ RInstance2.A reste égale à 2.
Writeln('RInstance1.A='
,RInstance1.A);
Writeln('RInstance2.A='
,Rinstance2.A);
Readln;
End
;
On constate la recopie membre par membre de RInstance1 vers RInstance2. Dans cet exemple nous avons bien deux instances différentes.
Passons à la modification d'un type référence :
Type
maClasse=Class
À :Integer
;
B :Integer
;
Constructor
Create(p1,p2:integer
);
end
;
Constructor
maClasse.Create(p1,p2:integer
);
begin
inherited
Create;
A:=p1;
B:=p2;
end
;
var
CInstance1, CInstance2 : maClasse;
Begin
CInstance1:= maClasse.Create(2
, 9
) ; // Création d'un objet
CInstance2:= CInstance1; // copie la référence d'Instance1 dans CInstance2
CInstance1.A:= 1
; // Le champ Instance2.A dans ce cas est aussi égal à 1.
Console.Writeline; // Appel d'une classe du Framework .NET
// Alias FCL ( Framework Classe Library)
Writeln('CInstance1.A='
,CInstance1.A);
Writeln('CInstance2.A='
,Cinstance2.A);
Console.readline;
End
;
Dans cet exemple on constate que CInstance1 référence bien CInstance2, nous avons bien deux instances identiques.
Sous Delphi .NET on voit donc qu'une classe est un type référence alors qu'un record est un type valeur.
Allons plus loin en mixant les deux types et essayons de récupérer une référence d'une variable de type valeur.
Ajoutons d'abord la déclaration d'une variable de type TObject qui permet de référencer n'importe quel objet :
var
OTemp: TObject;
Ensuite
OTemp:=RInstance1; // Récupére une référence sur RInstance1
RInstance1.A:=20
; // Modifie la variable référencée
Writeln('OTemp.A transtypé en monRecord, référence RInstance1.A='
,monRecord(OTemp).A);
Writeln('RInstance1.A référencé par OTemp='
,RInstance1.A);
Writeln;
Readln;
Nous nous apercevons que le résultat ne correspond pas à nos attentes. Ici l'instruction OTemp:=RInstance1; n'affecte pas l'adresse( la référence ) de la variable RInstance1, mais effectue une copie complète de l'objet RInstance1 dans OTemp avec allocation de la mémoire nécessaire !
Continuons notre démarche, essayons de modifier une variable de type valeur en lui assignant un type valeur :
Writeln('Assignation d''un type valeur avec une variable de type référence'
+#13#10
);
RInstance1:=MonRecord(OTemp);
Writeln('RInstance1.A est réassigné avec OTemp='
,RInstance1.A);
Writeln('OTemp.A='
,MonRecord(OTemp).A);
Writeln;
Readln;
Ici aussi il y a deux variables distinctes.
Un type valeur ne pouvant tout simplement pas se transformer en un type référence !
Par contre il est possible de convertir un type valeur en type référence, et inversement, en effectuant une conversion que l'on nomme Boxing et unBoxing.
Dans le source vous trouverez le dernier cas de figure qui concerne l'assignation de type référence qui lui ne pose pas de problème particulier de compréhension.
Par contre vous trouverez cette suite d'instructions qui est plus du 'bricolage' qu'autre chose :
try
OTemp:=Rinstance1;
CInstance1:=maClasse(OTemp);
Writeln('Test de bricolage '
,CInstance1.A);
Console.readline;
except
On
E: NullReferenceException do
Console.WriteLine('Erreur d''exécution la variable CInstance1 contient une référence nulle !'
);
end
;
end
;
Et ceci pour mettre en évidence qu'une variable instanciée de type référence peut se retrouver avec une assignation NULL, assignation qui provoquera l'exception NullReferenceException.
Voir aussi : Système de type commun. Local.
VII. Conversion▲
Certaines traductions d'ouvrage sur .NET associent le terme boxing à une notion de compartiment.
VII-A. Boxing▲
Projet : ..\Boxing-unBoxing\Boxing
Le boxing est une conversion explicite ( attention implicite en C# ) d'un type valeur en type objet ou bien vers n'importe quel type interface implémenté par ce type valeur. Lorsqu'une conversion boxing est appliquée à une variable de type valeur, une instance d'un objet est allouée et la valeur de cette variable est copiée dans le nouvel objet.
Examinons ce procédé dans l'exemple suivant :
var
VarTypeValeur : integer
; // Type valeur
VarTypeReference : TObject; // Type objet ( référence )
Begin
VarTypeValeur:= 123
;
VarTypeReference:=TObject(VarTypeValeur); // Boxing explicite
VarTypeValeur:= 456
; // Change le contenu de i
Writeln('Valeur de VarTypeValeur de type valeur = '
, VarTypeValeur);
Writeln('Valeur de VarTypeReference de type object (référence ) = '
, VarTypeReference);
L'instruction VarTypeReference:=TObject(VarTypeValeur) réalise explicitement une conversion boxing sur la variable VarTypeValeur. Il ne s'agit pas ici d'un simple transtypage, mais d'une opération à part entière masquée par le compilateur et prise en charge par le CLR.
Le boxing permet donc à chaque chose d'être vue comme un objet (un objet sous .NET étant un type référence).
À partir du moment où tout est objet on peut donc appeler les méthodes de System.Object sur les variables VarTypeValeur et VarTypeReference et particulièrement la méthode GetType qui renvoie de manière littérale le type de l'objet interrogé.
Ajoutons ces quelques lignes au début du programme précédent :
VarTypeReference:=TObject.Create; // L'appel de GetType nécessite l'appel du constructeur
// Gettype renvoie une valeur de type System.Type
Writeln('Nom complet du type de VarTypeValeur= '
, VarTypeValeur.GetType);
Writeln('Nom complet du type de VarTypeReference= '
, VarTypeReference.GetType);
L'affichage nous renvoie :
Nom complet du type de VarTypeValeur= System.Int32
Nom complet du type de VarTypeReference= System.Object
Ajoutons de nouveau les lignes d'affichage du type, mais cette fois-ci après les modifications et l'affichage du contenu des variables.
Nom complet du type de VarTypeValeur= System.Int32
Nom complet du type de VarTypeReference= System.Int32
La variable VarTypeReference est désormais de type System.Int32, le boxing modifie donc le contenu et le type de l'objet cible.
Je vous laisse vérifier ce que donne l'instruction suivante :
VarTypeReference:=TObject(321
.1
);
Note :
var
w : Word
; // Type valeur
i : integer
; // Type valeur
o : TObject; // Type objet ( référence )
Begin
i:= 123
;
o:=i;
...
Le code ci-dessus provoquera une erreur de compilation sur la seconde ligne à cause du typage fort de Delphi. Dans ce cas présent, le programmeur peut faire deux choses :
- soit expliciter le transtypage de la variable I vers un TObject :
o := TObject(I);
- soit ajouter la directive du compilateur AUTOBOX, directive qui est à utiliser avec précaution.
L'opération suivante n'est plus autorisée par le compilateur : Word(i) := w;
Voir aussi
Conversion Boxing. Local.
VII-B. UnBoxing▲
Projet : ..\Boxing-unBoxing\unBoxing
Il s'agit de l'opération inverse au boxing.
L'unboxing est une conversion explicite du type objet en un type valeur ou bien d'un type interface en un type valeur qui implémente l'interface.
Une conversion unboxing comprend les étapes suivantes :
- Vérification de l'instance de l'objet pour s'assurer qu'il s'agit bien d'une valeur convertie du type valeur spécifié.
- Copie de la valeur de l'instance dans la variable de type valeur.
Pour qu'une conversion unboxing vers un type valeur donné soit exécutée correctement, la valeur de l'argument source doit être une référence à un objet existant qui a été créé en effectuant une conversion d'une valeur du même type valeur.
Si l'argument source a la valeur nulle/Nil ou est une référence à un objet non pris en charge, une exception InvalidCastException est levée.
Examinons ce procédé dans l'exemple suivant :
var
j,i : integer
;
box : TObject;
begin
i:=312
; // Un type valeur
box:=TObject(i); // Boxing explicite
j:=integer
(box); // Unboxing explicite
Ajoutons une variable de type Byte et le code suivant :
var
h : byte
;
j,i : integer
;
box : TObject;
begin
...
H:=byte
(box);
Writeln('Nom complet du type de K= '
, H.GetType);
Writeln('Valeur de h, de type valeur, = '
, h);
L'assignation de H à partir de la variable box provoque l'erreur InvalidCastException à l'exécution.
Pourtant ce transtypage est sensé, mais il faut savoir que l'opération de unboxing doit se faire vers un type identique à celui utilisé lors de l'opération de boxing.
L'opération de transtypage doit donc se faire sur deux niveaux :
- le premier pour récupérer la valeur du type compartimenté.
- le second vers le type final tout en respectant les conventions de conversion de Delphi pour .NET.
Par exemple si le type contenu dans la variable box est de type double cette opération ne peut se faire qu'en utilisant l'instruction Trunc
var
h : Byte
;
box : TObject;
K : Double
;
begin
...
H:=byte
(integer
(box));
...
K:=3
.12
;
box:=TObject(K); // Boxing explicite
h:=byte
(Trunc(Double
(box))); // Transtypage des types réels en entiers nécessite Trunc
Voir aussi
Conversion unBoxing. Local.
VII-C. L'allocation mémoire▲
Projet : ..\AllocationArrayList\AllocationArrayList
À la différence de Win32, et en dehors de la création d'objets par son constructeur, il n'est plus nécessaire d'allouer explicitement de la mémoire pour manipuler un type enregistrement ou un type de base (nous verrons plus tard les constructeurs de type).
L'exemple suivant, en utilisant le boxing, nous le confirme :
//AllocationArrayList
uses
System.Collections;
Type
Data=Record
X,Y : Integer
;
end
;
var
D : Data;
Tab : Arraylist;
i : Integer
;
begin
Tab:=Arraylist.Create;
For
i:=0
to
10
do
begin
D.X:=i;
D.Y:=D.X*5
;
Tab.Add(D); // Ajout d'un record dans la liste,
// ne nécessite pas d'allocation mémoire pour les multiples instances de D.
// Box implicite
end
;
For
i:=0
to
10
do
Writeln('X= '
+Data(Tab[i]).X.ToString+' Y= '
+Data(Tab[i]).Y.ToString);
Readln;
end
.
L'allocation est réalisée lors de l'appel de la méthode Tab.Add, par une opération de boxing qui comme nous l'avons vu :
Lorsqu'une conversion boxing est appliquée à une variable de type valeur, une instance d'un objet est allouée et la valeur de cette variable est copiée dans le nouvel objet.
VIII. Passage de paramètres▲
Projet : ..\Passage_Parametre\Parametre
Les type valeur et type référence se comportent différemment dans un appel de méthode selon qu'ils sont passés soit en paramètre par valeur soit en paramètre par référence.
L'utilisation de var ou out permet de spécifier le passage de paramètre par référence, le mot clé out permettant de passer en paramètre une valeur qui n'a pas été initialisée.
L'utilisation de const ou l'absence de mot clé permet de spécifier le passage de paramètre par valeur.
Le tableau suivant récapitule les cas :
- Passage de types valeur par valeur.
Les modifications intervenant au sein de la méthode n'affectent en rien la valeur d'origine de la variable. - Passage de types valeur par référence.
La valeur du paramètre est modifiée après l'appel de la méthode. - Permutation de types valeur (deux entiers).
Vous devez passer les paramètres à la méthode Swap par référence - Passage de types référence par valeur.
La tentative de réassignation du paramètre à un emplacement de mémoire différent s'effectue uniquement au sein de la méthode et n'affecte pas la variable d'origine - Passage de types référence par référence.
Toutes les modifications qui interviennent dans la méthode affecteront les variables d'origine dans le programme appelant. - Permutation de types référence (deux chaines).
La permutation de chaines est un bon exemple de passage de paramètres de type référence par référence. Les deux chaines sont permutées au sein de la méthode, ainsi que dans l'appelant.
On voit donc que l'initialisation d'une variable globale, si toutefois cette notion a encore un sens sous .NET, se fera ainsi :
Procedure
Init_Variable( Out
uneRéference: unType);
VIII-A. Paramètre sans type▲
Projet : ..\Passage_Parametre\ParametreSansType
La déclaration d'un paramètre sans type dans un entête de méthode reste possible sous Delphi .NET . Dans ce cas la variable est vue comme une référence de type TObject à l'intérieur de la méthode. Un transtypage sera donc nécessaire pour la manipuler. De plus il est possible de lui assigner une nouvelle référence, mais la classe de cette nouvelle référence doit être une classe identique ou dérivée, mais pas une classe ancêtre. Ceci étant dû à mon avis au typage fort de .NET.
procedure
PrcAvecParametreSansType(var
X);
begin
Writeln('Type de la variable X : '
+TypeOf(x).ToString);
end
;
IX. Déclarations de types imbriqués dans les classes▲
Vous pouvez consulter l'article suivant concernant les déclarations de type imbriqué.
IX-A. Visibilité des types imbriqués.▲
Les membres (d'une classe ou d'un enregistrement) des types imbriqués peuvent avoir des visibilités différentes de la classe qui les héberge.
Déclarons une classe imbriquée comme ceci :
type
TForm1 = class
(TForm) //Déclaration de classe qui héberge une déclaration de classe imbriqué.
private
{ Déclarations privées}
public
Type
TSousClasse = Class
(Tobject) // Classe imbriquée
Private
MembrePrivé : Integer
;
Public
MembrePublic : Integer
;
End
;
{ Déclarations publiques}
public
SousClasse : TSousClasse;
end
;
Bien que TSousClasse soit public, de même que son instance (SousClasse), il ne sera pas possible d'accéder en dehors de l'unité à TForm1.SousClasse.MembrePrivé.
procedure
TForm2.Button1Click(Sender: TObject);
Var
i:Integer
;
begin
i:=Form1.SousClasse.MembrePublic; // Compile
i:=Form1.SousClasse.MembrePrivé; // Ne compile pas
end
;
Je remercie Nono40 pour cet exemple.
X. Alias de type▲
Projet : ..\Type\Type_type
Delphi permettait déjà de créer des alias de type tels que
type
TMaString = string
;
autorisant la déclaration de variable suivante :
var
Chaine1: TMaString;
Chaine2: string
;
Ici Chaine1 et Chaine2 sont du même type.
Si on souhaite distinguer le type exact de ces deux variables, cela n'est pas possible, car TMaString est un nom de type différent de string, mais si on veut créer un autre type, vu comme tel par le compilateur, il faut utiliser la syntaxe suivante :
type
TMaString = type
string
;
Dans ce cas le passage en paramètre par référence d'une variable de ce type provoquera une erreur de compilation :
type
TMaString1 = string
;
TMaString2 = type
string
;
var
Chaine: string
;
Chaine1: TMaString1;
Chaine2: TMaString2;
procedure
Affiche(S:TMaString2);
begin
end
;
procedure
Affiche_Var(Var
S:TMaString2);
begin
end
;
begin
Chaine:='Type Chaine classique'
;
Chaine1:=Chaine;
// l'affectation ne pose pas de problème
Chaine2:=Chaine;
// Le passage en paramètre à une procédure const ou par valeur ne pose pas de problème
Affiche(Chaine1);
// Le passage en paramètre à une procédure var, provoque l'erreur (E2033) :
// Les types des paramètres var originaux et formels doivent être identiques.
Affiche_Var(Chaine1);
Le code précédent provoque l'erreur suivante :
E2033 : Les types des paramètres var
originaux et formels doivent être identiques.
XI. Identificateurs étendus▲
Texte issu de la documentation de DELPHI 2005 :
Lors de la programmation avec Delphi pour .NET, il se peut que vous rencontriez des identificateurs CLR (c'est-à-dire des types ou des méthodes de classe) de même nom qu'un mot clé Delphi. Par exemple, une classe peut posséder une méthode appelée begin. La classe CLR Type de l'espace de nommage System constitue un autre exemple. Type est un mot clé du langage Delphi qui ne peut être utilisé comme nom d'identificateur.
En revanche, vous pouvez sans problème qualifier l'identificateur à l'aide de sa spécification complète de l'espace de nommage. Pour utiliser la classe Type, par exemple, vous devez utiliser son nom complet :
var
TMyType : System.Type
; // L'utilisation de l'espace de nommage qualifié
// évite toute ambiguïté avec le mot clé Delphi.
Comme alternative abrégée, Delphi pour .NET propose l'opérateur perluète (&) afin de résoudre les ambiguïtés entre identificateurs CLR et mots-clé du langage Delphi. Si vous rencontrez une méthode ou un type portant le même nom qu'un mot clé Delphi alors que vous avez indiqué le signe & au début de l'identificateur, vous pouvez omettre la spécification de l'espace de nommage.
Par exemple, le code suivant utilise un « et commercial » pour distinguer la classe CLR Type du mot clé Delphi type.
var
TMyType : &Type
; // Le mot commence par '&' ce qui le distingue du mot clé.
Un autre exemple dans un contexte de conversion de code C# vers Delphi.
XII. Itérateurs▲
Vous pouvez consulter l'article suivant concernant les itérateurs.
var
I, odd, even : integer
;
arr : Array
[] of
integer
;
Begin
odd:= 0
;
even:=0
;
arr:= New(array
[] of
integer
,(0
,1
,2
,5
,7
,8
,11
));
For
I in
arr Do
begin
WriteLn('I= '
+ intToStr(I));
if
(I mod
2
= 0
)
then
begin
inc(even);
WriteLn('Trouvé '
+ intToStr(even)+' '
+intToStr(I));
end
else
inc(odd);
end
;
WriteLn('Trouvé '
+ intToStr(odd)+' chiffre impair et '
+intToStr(even)+' chiffre pair.'
) ;
Readln;
Attention ce qui différencie une boucle classique d'un itérateur est qu'on manipule via la variable d'indice I le contenu de la collection.
La variable I ne contient pas le nombre d'itérations.
Cette syntaxe peut prêter à confusion dans les premiers temps.
Rappel des différentes possibilités :
// Tableau
const
MyArr : Array
[0
..3
] of
char
= ('1'
,'2'
,'3'
,'4'
);
var
C : Char
;
S : String
;
begin
for
C in
MyArr do
S := S + C;
ShowMessage(S);
end
;
// Ensemble
const
MySet: set
of
char
= ['1'
,'2'
,'3'
,'4'
];
var
C : Char
;
S : String
;
begin
for
C in
MySet do
S := S + C;
ShowMessage(S);
end
;
// Chaine de caractère (Strings)
const
S : String
= 'C''est une chaine intéressante'
;
var
C : Char
;
cnt: Integer
;
begin
cnt := 0
;
for
C in
S do
if
C = 'e'
then
Inc (cnt);
ShowMessage(Format('Votre chaine contient %d lettres "e"'
,[cnt]));
end
;
// Collection prédéfinie TStringLists
var
S : String
;
begin
for
S in
Memo1.Lines do
ListBox1.Items.Add(S);
end
;
Projet : ..\class-iterateur\class_iterateur
L'itération sur une classe demande un peu plus de code :
type
TPeopleEnumerator = class
; // Forward déclaration
// Objet manipulé
TPerson = class
Name: String
;
DateOfBirth: TDateTime;
Gender : Char
;
constructor
Create (AName: String
; ADateOfBirth: TDateTime; AGender: Char
);
end
;
// Liste d'objets, conteneur pour le type TPerson
TPeopleList = class
strict
private
List : TList;
function
GetCount: Integer
;
function
GetPerson(idx: Integer
): TPerson;
procedure
SetPerson(idx: Integer
; const
Value: TPerson);
public
Constructor
Create;
function
Add (Person: TPerson): Integer
;
procedure
Delete (idx: Integer
);
property
Person [idx: Integer
] : TPerson read
GetPerson write
SetPerson; default
;
property
Count: Integer
read
GetCount;
//Méthode nécessaire pour la prise en charge de l'itération
Function
GetEnumerator: TPeopleEnumerator;
end
;
// Afin que la collection soit énumérable, elle doit implémenter une suite de méthodes
// comme indiqué ci-dessous.
// Énumérateur pour la classe conteneur.
TPeopleEnumerator = class
strict
private
CurrentPos: Integer
;
PeopleList : TPeopleList;
public
constructor
Create (AList : TPeopleList);
function
GetCurrent: TPerson;
//Membre de classe nécessaire pour la prise en charge de l'itération
function
MoveNext :Boolean
; // Méthode publique
property
Current : TPerson read
GetCurrent; // Propriété en Read Only
end
;
Une fois les méthodes implémentées, l'itération de la classe devient possible :
//Exemple d'énumération de la classe TPeopleList
// Le membre PeopleList appartient à la Forme principale
Procedure
TForm1.Button1Click(Sender: TObject);
var
Person : TPerson; // Doit être une variable locale
begin
// Ajout d'élément
PeopleList.Add(TPerson.Create('Daniel'
,StrToDate('15/03/1973'
),'M'
));
PeopleList.Add(TPerson.Create('Monique'
,StrToDate('05/05/1973'
),'F'
));
// Itération sur la classe
for
Person in
PeopleList do
ListBox1.Items.Add(Person.Name);
end
;
La variable utilisée pour l'itération doit être de même type que celle déclarée dans la classe cible et cette variable doit être locale à la méthode ou à la procédure.
XIII. Directives de compilation▲
Vous pouvez consulter l'article suivant concernant les nouvelles directives.
XIII-A. Autobox▲
{$AUTOBOX ON}
La directive AUTOBOX génère un typage faible. L'employer peut faciliter l'écriture, mais peut également amener plus d'erreurs de programmation.
Son utilisation force le boxing c'est-à-dire qu'elle évite au programmeur les opérations de transtypage nécessaires sans sa présence.
XIII-B. Experimental▲
Il s'agit d'une directive de conseil. Vous pouvez utiliser cette directive pour désigner des unités qui sont dans un état de développement instable. Le compilateur émet un avertissement quand il construit une application qui utilise l'unité.
unit
Borland.Vcl.NetControl platform
experimental;
XIV. Compilation conditionnelle▲
Nom |
Définition |
---|---|
VER170 |
Version du compilateur Delphi 2005. La documentation est erronée. |
CLR |
Indique que le code sera compilé pour la plate-forme .NET. |
WIN32 |
WIN32 Indique que le système d'exploitation est Win32 API. |
XV. Commutateurs de la ligne de commande de l'IDE▲
Un nouveau commutateur, debugger=[borwin32|bordonet], vous permet de sélectionner le débogueur Win32 de Borland ou le débogueur .NET en ligne de commande du SDK, lorsque vous déboguez à partir de la ligne de commande.
Lors de l'utilisation du commutateur -LU sur la plate-forme .NET, vous pouvez faire une référence au package avec ou sans l'extension .dll.
Si vous omettez l'extension .dll, le compilateur recherchera le package sur le chemin de recherche de l'unité et sur le chemin de recherche du package. Néanmoins, si la spécification de package contient une lettre de lecteur ou le caractère séparateur de chemin, le compilateur suppose alors que le nom de package est le nom de fichier complet (y compris l'extension .dll). Dans le dernier cas, si vous spécifiez un chemin relatif ou complet, mais omettez l'extension .dll, le compilateur ne sera pas capable de localiser le package.
Voir aussi
XVI. Ce qui est déconseillé▲
Il n'est plus possible, dans du code managé, d'utiliser les déclarations ou procédures suivantes :
pointeurs, type procédure, exitproc, absolute, @, les types pchar et variants…
Pour la manipulation de délégués l'opérateur @ reste possible, mais c'est une exception.
Les procédures de manipulation de mémoire : move, fillchar, getmem, freemem…
Et bien évidemment la directive asm, l'accès aux registres du microprocesseur n'est plus possible dans du code managé ( .NET ou JAVA ).
Et ceci pour la simple raison qu'ils sont considérés comme type ou code non sécurisé ( unsafe ).
Les types pointeur et pointeur de fonction ne sont pas conformes CLS. Certains types de données ne sont plus supportés et sont soit émulés soit remplacés par d'autres types natifs de .NET.
Le tableau suivant montre la correspondance entre les types réels fondamentaux et les types .NET Framework.
Mappage des types réels .NET fondamentaux
Type |
Mappage .NET |
---|---|
Real48 |
Deprecated |
Single |
Single |
Double |
Double |
Extended |
Double |
Comp |
Deprecated |
Currency |
Réimplémenté comme un type de valeur en utilisant le type Decimal du .NET Framework |
Si votre composant doit être entièrement utilisable par des composants écrits dans d'autres langages, les types exportés de votre composant doivent exposer uniquement les fonctionnalités de langage qui font partie de la spécification CLS (Common Language Specification).
Voir aussi
Deprecated Language Features
Aide de delphi 2005 :Portage d'applications VCL vers Delphi 2005
XVII. Les délégués▲
Projet : ..\Delegues\Delegate1
Un délégué en Delphi .NET est similaire à un pointeur de fonction ou de procédure en WIN32. Avec un délégué, vous pouvez encapsuler une référence à une méthode à l'intérieur d'un objet delegate. Ce dernier peut ensuite être transmis au code, qui peut appeler la méthode référencée sans savoir, au moment de la compilation, quelle méthode sera appelée. Toutefois, à la différence des pointeurs de procédure ou de fonctions, les délégués sont orientés objet, indépendants des types et sécurisés.
Une de leur utilisation concerne les fonctions de rappel (Callback).
En Delphi .NET les types procédure sont donc implémentés comme des types délégués. Leur déclaration se fait par l'ajout de of object au nom de type. Exemple :
TMonDelegue= Procedure
(AClient:TClient) of
Object
;
Les déclarations suivantes sont toutes compilées comme des délégués :
Type
TMonDelegue = Procedure
of
Object
;
TMonDelegue2 = Procedure
;
TMonDelegueF = Function
:Boolean
;
var
unDelegue : procedure
(L :integer
);
L'utilisation se fait ensuite de la manière suivante :
TCompte=Class
Clients:Array
[1
..5
] of
TClient;
Constructor
Create;
// Déclaration
procedure
Callback(UnDelegue:TMonDelegue);
end
;
...
// Implémentation
procedure
TCompte.Callback(UnDelegue:TMonDelegue);
var
i: integer
;
begin
For
i:=1
to
5
do
//Callback
UnDelegue(Clients[i]);
end
;
...
// Main
Procedure
AfficheClient(UnClient:TClient);
begin
Writeln('Procédure de callback appelée pour le client nommé : '
,UnClient.Nom);
end
;
begin
UnCompte:=TCompte.Create;
// Appel
UnCompte.Callback(AfficheClient);
Il est tout à fait possible de manipuler ce délégué par l'intermédiaire d'une variable typée :
Var
TestDelegue:TMonDelegue;
..
// Autre construction possible
TestDelegue:=AfficheClient;
UnCompte.Callback(TestDelegue);
Projet : ..\Delegues\Delegate2
Ensuite il est possible de visualiser la différence qui ne semble pas évidente entre une déclaration d'un type procédure sous Win32 et d'un délégué sous Delphi .NET:
// Assignation obligatoire, sinon exception NullReferenceException lors de l'appel de GetType
TestDelegue:=AfficheClient;
Writeln('Ancêtre de TestDelegue = '
,typeof(TestDelegue).BaseType);
L'instruction suivante provoque l'erreur de compilation suivante :
Pas assez de paramètres originaux (E2035)
La syntaxe correcte ici se fait par l'utilisation de l'opérateur @ qui semblait déconseillé. Il s'agit ici de la seule exception.
Il permet d'obtenir l'adresse du délégué. Si vous n'employez pas cette syntaxe, le compilateur pensera que vous essayez d'appeler le délégué, d'où l'erreur précédente.
Writeln('Ancêtre de TestDelegue = '
,typeof(@TestDelegue).BaseType);
// Ici le compilateur reconnaît un accès à un membre de classe.
Writeln('Type de TestDelegue = '
,TestDelegue.GetType);
L'affichage de la classe ancêtre de TestDelegue nous renvoie System.MultiCastDelegate qui est elle-même dérivée de la classe System.Delegate.
En quoi notre procédure est-elle à diffusion multiple (multicast) ?
XVII-A. La diffusion multiple▲
Projet : ..\Delegues\Delegate3
Cet exemple nous permet d'aborder une nouveauté fort appréciable du langage Delphi pour .NET.
On peut désormais par un appel de procédure, notre délégué utilisé précédemment, enchaîner l'appel de plusieurs procédures et modifier ce chaînage au gré des besoins !
Nous avons vu jusqu'ici l'information multi, portée par le nom de classe; en associant cette mécanique aux événements (messages) on retrouve l'information de diffusion (cast), on peut donc désormais déclencher simplement des actions multiples à la suite de la réception d'un message.
Sous Delphi .NET, la déclaration d'une propriété est obligatoire pour implémenter ces fonctionnalités.
Voici la déclaration d'un délégué single-cast :
Property
MonDelegue:TMonDelegue read
FMonDelegue write
FMonDelegue;
La déclaration d'un délégué multicast nécessite l'utilisation de nouveaux mots clés, concernant les propriétés, qui sont add et remove.
Leur présence signale au compilateur qu'il autorise le chaînage :
Property
MonDelegue:TMonDelegue add FMonDelegue remove FMonDelegue;
La différence entre les deux déclarations est que pour la première il n'y a qu'un seul appel possible, il s'agit du dernier délégué ajouté à 'la liste de délégués' via la méthode include. Voyons dans le détail ce comportement.
On doit donc dans un premier temps modifier la classe TCompte et le prototype de sa procédure Callback :
TCompte=Class
Clients:Array
[1
..5
] of
TClient;
// Pour le multicast doit être une propriété :
FMonDelegue:TMonDelegue;
Constructor
Create;
procedure
Callback;
// la syntaxe : Property MonDelegue:TMonDelegue read FMonDelegue write FMonDelegue;
// ne permet pas la composition de délégué. Il n'y a qu'un seul appel qui est le dernier déclaré
Property
MonDelegue:TMonDelegue add FMonDelegue remove FMonDelegue;
end
;
Ensuite dans l'implémentation on doit appeler le délégué via la propriété :
procedure
TCompte.Callback;
var
i: integer
;
begin
if
assigned(FMonDelegue) then
For
i:=1
to
5
do
FMonDelegue(Clients[i]);
end
;
Et pour finir l'ajout d'un délégué dans la liste se fait par l'appel de la procédure dédiée Include.
Include(UnCompte.MonDelegue,@Traite);
Include(UnCompte.MonDelegue,@AfficheClient);
UnCompte.Callback;
La suppression d'un délégué dans la liste des délégués se fait par l'appel de la procédure dédiée Exclude.
Exclude(UnCompte.MonDelegue,@Traite);
Exclude(UnCompte.MonDelegue,@AfficheClient);
Include(UnCompte.MonDelegue,@Fin);
UnCompte.Callback;
Il reste possible d'assigner une méthode à délégué comme ceci :
UnCompte.FMonDelegue:=Traite;
UnCompte.Callback;
mais dans ce cas le compilateur crée un nouvel objet délégué et lui affecte la méthode indiquée. La suppression de l'ancienne référence contenue dans FMonDelegue étant prise en charge par le Garbage Collector.
La suppression complète peut se faire par l'affectation de NIL au délégué :
// Identique à System.Delegate.RemoveAll(System.Delegate(@FMultiEvent), Nil);
UnCompte.FMonDelegue:=Nil
;
UnCompte.Callback;
XVII-B. Gestionnaire d'événements sous la VCL▲
Projet : ..\Delegues\VCL\Delegues
Vous trouverez dans cet exemple une utilisation d'un délégué au sein d'une Fenêtre VCL (TForm).
Il est possible de retrouver le contenu de la liste des délégués :
type
//Tableau Dynamique de Délégués
TDelegateDynArray = array
of
System.Delegate;
...
procedure
TForm1.Button1Click(Sender: TObject);
var
TabDelegue : TDelegateDynArray;
unDelegue : System.Delegate;
begin
if
Not
assigned(FMultiEvent)
then
begin
Log.Lines.Add('La liste des délégués est vide'
);
Exit;
end
;
TabDelegue:=System.Delegate(@FMultiEvent).GetInvocationList;
For
unDelegue in
TabDelegue do
begin
// Obtient le type du Delegué, ici : TNotifyEvent
// log.Lines.Add('Array :'+Deleg.ToString);
Log.Lines.Add('Array :'
+' '
+unDelegue.Target.ToString+'.'
+unDelegue.Method.Name);
end
;
end
;
Il est tout à fait possible de tester si deux listes de délégués contiennent ou non des méthodes identiques.
Consultez la documentation du SDK à propos des tests d'égalité sur des instances de délégués : Delegate.Equals …
Voir aussi :
Délégué EventHandler. Local
Classe EventArgs. Local
XVII-C. Gestionnaire d'événements sous Windows Form▲
La gestion des événements sous Windows Form est différente, on doit utiliser un prototype de délégué spécifique :
unit
Borland.Vcl.NetControlWrapper platform
experimental;
...
TNetEventHandler = procedure
(Sender: System.Object
; AArgs: System.EventArgs);
...
Notez que la convention de nommage, préconisé par Microsoft, est d'ajouter le suffixe EventHandler. Libre à vous de la suivre.
L'entête de la méthode du délégué contient un premier paramètre de type System.Object et un deuxième de type System.EventArgs. Par convention, le premier paramètre est toujours l'instance qui a appelé le gestionnaire d'événement, le second contient des arguments décrivant l'événement.
La classe System.EventArgs ne contient pas de donnée d'événement; elle est utilisée par des événements qui ne passent pas d'informations d'état à un gestionnaire d'événements lorsqu'un événement est déclenché. Si le gestionnaire d'événements nécessite des informations d'état, l'application doit dériver une classe à partir de cette classe pour contenir les données.
type
MyEventArgs=Class
(EventArgs)
UserPrintPageEventArgs = class
(EventArgs)
Public
MySpecialValue :String
;
end
;
TSampleEventHandler = procedure
(Sender: TObject; Args: MyEventArgs);
Lorsque le délégué est appelé, le gestionnaire a accès à Args.MySpecialValue.
Voir aussi :
Didacticiel sur les événements. Local
System.EventArgs. Local
Délégué EventHandler. Local
XVIII. Syntaxe d'assistance de classe ( Class Helper )▲
Texte issu de la documentation de DELPHI 2005
Une assistance de classe est un type qui, lorsqu'il est associé à une autre classe, introduit des méthodes et propriétés supplémentaires qui peuvent être utilisées dans le contexte de la classe associée (ou ses descendants). Les assistances de classes permettent d'étendre une classe sans avoir recours à l'héritage.
Les assistances de classes permettent d'étendre une classe, mais elles ne doivent pas être considérées comme un outil de conception à utiliser pour développer un nouveau code. Elles ne doivent être utilisées que pour la liaison entre le langage et la RTL de la plate-forme.
On peut voir ci-dessous l'utilisation parcimonieuse des class Helper dans les sources du runtime de Delphi 2005 .NET.
...\Borland\BDS\3.0\source\dotNet\rtl\
Borland.Delphi.System.pas : TObjectHelper = class helper for TObject
Borland.Vcl.Classes.pas) : TPersistentHelper = class helper (TObjectHelper) for TPersistent
Borland.Vcl.Classes.pas : TComponentHelper = class helper (TPersistentHelper) for TComponent
Borland.Vcl.SysUtils.pas : ExceptionHelper = class helper (TObjectHelper) for Exception
Borland.Vcl.TypInfo.pas : TTypeInfoHelper = class helper for TTypeInfo
Borland.Vcl.TypInfo.pas : TPropInfoHelper = class helper for TPropInfo
Borland.Vcl.Variants.pas : VariantHelper = class helper for Variant
Borland.Vcl.Variants.pas : OleVariantHelper = class helper for OleVariant
Toutes les classes concernées sont des classes spécifiques à Delphi et mappées sur les classes correspondantes du Framework .NET. Par contre ces classes du Framework ne déclarent pas certains membres présents dans la classe Delphi.
Le class Helper permet donc ici d'ajouter ces membres manquants afin de préserver la compatibilité du code existant.
ExceptionHelper = class
helper (TObjectHelper) for
Exception
private
class
function
CreateMsg(const
Msg: string
): Exception;
function
GetHelpContext: Integer
;
procedure
SetHelpContext(AHelpContext: Integer
);
public
/// Doc: The help context return zero(0) if exception's helplink property cannot be parsed into an integer.
property
HelpContext: Integer
read
GetHelpContext write
SetHelpContext;
// constructor Create(const Msg: string) is provided by the CLR class
class
function
CreateFmt(const
Msg: string
; const
Args: array
of
const
): Exception;
class
function
CreateHelp(const
Msg: string
; AHelpContext: Integer
): Exception;
class
function
CreateFmtHelp(const
Msg: string
; const
Args: array
of
const
;
AHelpContext: Integer
): Exception;
// System.Exception.HResult is protected. This helper exposes
// the HResult for read-only access by non-descendents
function
HResult: Integer
;
end
;
ExceptionClass = class
of
Exception;