I. Rappel▲
Vous trouverez dans le tutoriel suivant le rappel des évolutions du langage sous Delphi 2005 - Win32.
Pour un aperçu sur certaines évolutions du langage identique sur les plateformes Win32 et .NET, consultez le tutoriel sur le langage Delphi .NET.
II. Alias de 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.
III. Nouveaux spécificateurs de visibilité pour les membres de classe▲
Delphi ajoute deux spécificateurs de visibilité supplémentaires strict private (privée stricte) et strict protected (protégée stricte). Ils sont ordonnés ainsi, bien que dans une déclaration de classe cette notion d'ordre ne soit pas effective :
- strict private ;
- private ;
- strict protected ;
- protected ;
- public.
Texte issu de la documentation de DELPHI 2006 bêta
Les membres de classes dont la visibilité est strict private ne sont accessibles que dans la classe dans laquelle ils sont déclarés. Ils ne sont pas visibles pour les procédures ou fonctions déclarées dans la même unité.
Les membres de classes dont la visibilité est strict protected sont visibles dans la classe dans laquelle ils sont déclarés et dans toutes les classes dérivées, quel que soit l'endroit où elles sont déclarées.
Rappel :
Les membres au début de la déclaration d'une classe dont la visibilité n'est pas spécifiée sont par défaut public.
TMaClasse = class
strict
private
// Visible seulement par les instances de la classe MaClasse
FStrictPrivate: Integer
;
private
// Visible seulement par les instances de la classe MaClasse ou de l'unité
Fprivate: Integer
;
strict
protected
// Visible seulement par les instances de la classe MaClasse ou des classes dérivées
FStrictProtected: Integer
;
protected
// Visible seulement par les instances de la classe MaClasse, des classes dérivées ou de l'unité
FProtected: Integer
;
public
// Visible par tout code qui dispose d'un accès global à cette classe.
FVisible: Integer
;
published
// Rend publique cette partie et génère également les informations de types à l'exécution.
// Entre autres rôles, les informations de types à l'exécution permettent à l'inspecteur
// d'objets d'accéder aux propriétés et aux événements.
property
Visible: Integer
read
FVisible;
end
;
Vous pouvez, dans une classe, déclarer un constructeur privé afin d'empêcher la création d'instances de cette classe.
IV. Variable de classe▲
Texte issu de la documentation de DELPHI 2006 bêta
Les champs de classe sont des champs de données d'une classe accessibles sans une référence d'objet.
La déclaration de bloc class var vous permet d'introduire un bloc de champs de classe dans une déclaration de classe.
Un bloc class var se termine par :
- une autre déclaration class var ;
- une déclaration de procédure ou fonction (c'est-à-dire une méthode) (y compris des procédures de classe et des fonctions de classe) ;
- une déclaration de propriété (y compris des propriétés de classe) ;
- une déclaration de constructeur ou de destructeur ;
- un spécificateur de portée de visibilité (public, private, protected, published, strict private et strict protected).
Par exemple :
TDBBool = record
Private
// Champ privé pour stocker la valeur du type
FValeur : integer
;
Constructor
Create(AValeur: IDBType);
procedure
SetValeur(AValeur: IDBType);
Public
// Champs statiques de classe.
Class
var
dbFalse: TDBBool;
Class
var
dbNull : TDBBool;
Class
var
dbTrue : TDBBool;
...
Il est préférable de répéter la déclaration de class var, car dans certains cas vous rencontrerez quelques problèmes :
Public
// champs de classe, static
Class
var
dbFalse: TDBBool;
dbNull : TDBBool;
dbTrue : TDBBool;
// private
FValeur : integer
;
La mise en commentaire de Private provoque un effet de bord.
Le code suivant permet d'accéder aux champs de classe, déclarés ci-dessus, en préfixant le nom du champ avec le nom de la classe propriétaire :
Begin
TDBBool.dbNull.FValeur:=cdbNull;
TDBBool.dbFalse.FValeur:=cdbFalse;
TDBBool.dbTrue.FValeur:=cdbTrue;
end
;
Les variables de classe sont accessibles dans les classes dérivées :
Type
TMaClass= Class
VariableInstance :String
;
class
var
VariableDeClasse_TMaClass :String
;
end
;
TDescendant= Class
(TMaClass)
class
var
VariableDeClasse_TDescendant :String
;
end
;
var
Test : TMaClass;
TestDescendant: TDescendant;
begin
Test.VariableDeClasse_TMaClass:='Test'
;
TDescendant.VariableDeClasse_TDescendant:='Descendant'
;
TestDescendant.VariableDeClasse_TDescendant:='Autre appel possible'
;
TDescendant.VariableDeClasse_TMaClass:='Modification'
;
end
.
V. Propriété de classe▲
Les propriétés de classe sont des propriétés d'une classe accessibles sans une référence d'objet.
Une propriété de classe ne peut pas être publiée et ne peut pas avoir de définition de valeur stockée ou par défaut. De plus le champ associé à la propriété de classe doit être au minimum un champ de classe et les possibles accesseurs de propriétés de classes doivent être déclarés comme méthodes statiques de classe.
Le non-respect de ces deux règles provoquera l'erreur de compilation suivante :
E2355 : L'accesseur de la propriété de classe doit être un champ de classe ou une méthode statique de classe.
Voici un exemple d'utilisation :
type
TMaClass = class
strict
private
class
var
FY : Integer
;
class
var
FX : Integer
;
strict
protected
// Remarque : les accesseurs des propriétés de classe doivent être déclarés comme méthodes de classe statiques.
class
function
GetX: Integer
; static
;
class
procedure
SetX(val: Integer
); static
;
public
class
property
X: Integer
read
GetX write
SetX;
class
property
Y: Integer
read
FY write
FY;
end
;
class
function
TMaClass.GetX: Integer
;
begin
Result:=FX;
end
;
class
procedure
TMaClass.SetX(val: Integer
);
begin
FX:=val;
end
;
L'accès aux propriétés de classe se fait de la manière suivante :
begin
TMaClass.X:=9
;
TMaClass.y:=7
;
Writeln(TMaClass.X,' '
,TMaClass.Y);
Readln;
end
.
VI. Surcharge de propriété indexée▲
Il est possible de surcharger la définition d'une propriété afin d'utiliser un ou plusieurs index de types différents. Si une classe a une propriété par défaut, vous pouvez accéder à cette propriété en utilisant la forme abrégée object[index] équivalente à object.property[index].
Par exemple, MonObjet.Strings[7] peut s'abréger en MonObjet[7]. Une classe ne peut avoir qu'une seule propriété par défaut.
type
TMaClass = class
FList : Array
[1
..10
] of
String
;
function
GetItem(Index
: string
): string
; overload
;
procedure
SetItem(Index
:string
; Value: string
); overload
;
function
GetItem(Index
: Integer
): string
; overload
;
procedure
SetItem(Index
: Integer
; Value: string
); overload
;
property
Item[Index
: string
]: string
read
GetItem write
SetItem; default
;
property
Item[Index
: integer
]: string
read
GetItem write
SetItem; default
;
end
;
Les deux propriétés sont déclarées avec le mot clé default sinon il n'est pas possible de compiler cet exemple. Il semblerait que la surcharge ne soit possible que pour la propriété par défaut d'une classe.
Voici un exemple d'appel :
var
Propriete : TMaClass;
begin
Propriete:=TMaClass.Create;
Propriete[1
]:='Premier'
;
Affiche(Propriete);
Writeln('Accès integer '
+Propriete[1
]);
Writeln('Accès String '
+Propriete['Premier'
]);
readln;
Propriete['Premier'
]:='Deux'
;
...
end
;
VII. Méthode Final▲
Cette nouvelle fonctionnalité permet à Delphi d'utiliser le concept de méthode virtuelle finale. Quand le mot clé final est appliqué à une méthode virtuelle, aucune classe dérivée ne peut redéfinir cette méthode.
L'usage du mot clé final est une décision de conception importante qui permet de définir l'utilisation de la classe.
program
MethodeFinal;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMaClass= class
(TObject)
Champ1 : integer
;
procedure
FaitqqChose; virtual
;
end
;
TDescendantClass= class
(TMaClass)
Champ2 : integer
;
procedure
FaitqqChose; override
; Final
;
end
;
TClassRedeclareMethodeFinal= class
(TDescendantClass)
Champ3 : integer
;
procedure
FaitqqChose; override
; //Erreur
// E2352 : Impossible de redéfinir une méthode finale
end
;
procedure
TMaClass.FaitqqChose;
begin
Writeln('Champ1 '
,Champ1);
end
;
procedure
TDescendantClass.FaitqqChose;
begin
Writeln('Champ2 '
,Champ2);
end
;
var
MonInstance: TClassRedeclareMethodeFinal;
begin
MonInstance:=TClassRedeclareMethodeFinal.Create;
end
.
L'ajout du mot clé final à la déclaration de la méthode FaitqqChose; override de la classe TDescendantClass provoque l'erreur de compilation suivante pour la déclaration de la classe TClassRedeclareMethodeFinal :
E2352 : Impossible de redéfinir une méthode finale.
VIII. Méthode statique de classe▲
Dans la version bêta de Delphi 2006 le comportement de cette directive pose quelques problèmes.
Texte issu de la documentation de DELPHI 2006 bêta
Les méthodes statiques de classe sont des méthodes d'une classe accessibles sans une référence d'objet. Elles fonctionnent de la même manière que les méthodes traditionnelles de classe sous Delphi Win32.
Pour rendre une méthode de classe statique, ajoutez le mot static à sa déclaration, par exemple
program
MethodeStatic;
{$APPTYPE CONSOLE}
Type
TMaClass= Class
VariableInstance :String
;
class
var
VariableDeClasse :String
;
Class
procedure
Affiche2; // Méthode de classe
Class
procedure
Affiche; static
; // Méthode statique de classe
procedure
Traitement;
end
;
Class
procedure
TMaClass.Affiche;
begin
VariableDeClasse :='Affiche : une seule occurrence pour cette variable de classe'
;
// La ligne suivante est gérée par le compilateur et n'est pas possible.
//VariableInstance :='Plusieurs occurrences pour cette variable d''instance.';
Writeln(VariableDeClasse);
//Writeln(Self.ClassName); // E2003 : Identificateur non déclaré
end
;
Class
procedure
TMaClass.Affiche2;
begin
//VariableDeClasse :='Affiche2 : une seule occurrence pour cette variable de classe';
Writeln(VariableDeClasse);
if
assigned(Self
)
then
Writeln(Self
.ClassName);
end
;
procedure
TMaClass.Traitement;
begin
Writeln('Dans la méthode traitement'
);
end
;
var
Test : TMaClass;
begin
// Appel de méthode de classe.
TMaClass.Affiche2;
Test.Affiche2;
Test.Affiche;
Test:=TMaClass.Create; // Self accessible dans Affiche2
Test.Affiche2;
Readln;
end
.
Contrairement aux méthodes de classe Delphi Win32, les méthodes statiques de classe n'ont pas de paramètre Self et ne peuvent donc accéder à aucun membre de classe, en dehors des champs de classe. En outre les méthodes statiques de classe ne peuvent pas être déclarées virtuelles (Virtual).
IX. Classe scellée (sealed)▲
Cette nouvelle fonctionnalité permet à Delphi de déclarer une classe qui ne peut pas être étendue par le biais de l'héritage.
Une fois une classe déclarée scellée (sealed) elle ne peut plus être dérivée comme le montre l'exemple suivant :
program
ClasseSealed;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMaClass= class
(TObject)
Champ1 : integer
;
procedure
FaitqqChose; virtual
;
end
;
TSealedClass= class
sealed
(TMaClass)
Champ2 : integer
;
procedure
FaitqqChose; override
;
end
;
// Erreur à la compilation
TImpossibleClass= class
(TSealedClass)
Champ3 : integer
;
procedure
FaitqqChose; override
;
end
;
procedure
TmaClass.FaitqqChose;
begin
Writeln('Champ1 '
, Champ1);
end
;
procedure
TSealedClass.FaitqqChose;
begin
Writeln('Champ2 '
, Champ2);
end
;
var
MonInstance: TSealedClass;
begin
MonInstance:=TSealedClass.Create;
end
.
L'ajout du mot clé sealed dans la déclaration de classe TSealedClass= class (TMaClass) provoque l'erreur de compilation suivante pour la déclaration de la classe TImpossibleClass :
E2353 : Impossible d'étendre la classe sealed 'TSealedClass'
X. Classe abstraite▲
Dans la version bêta de Delphi 2006 le mot clé abstract est reconnu par le compilateur, mais son ajout ne modifie pas le comportement de la classe.
Cette nouvelle fonctionnalité permet à Delphi de déclarer la totalité d'une classe comme classe abstraite (abstract) ce qui implique que cette classe ne peut pas être instanciée. Une classe entière peut être déclarée abstraite même si elle ne contient aucune méthode virtuelle abstraite.Une classe entière peut être abstrait avoué même si elle ne contient aucune méthode virtuelle abstraite.
La syntaxe de déclaration est la suivante :
program
Abstract
;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TAbstractClass= class
abstract
(TObject)
ChampExistant : integer
;
procedure
FaitqqChose;
end
;
procedure
TAbstractClass.FaitqqChose;
begin
Writeln('ChampExistant '
,ChampExistant);
end
;
var
MonInstance:TAbstractClass ;
begin
MonInstance:=TAbstractClass.Create; // Impossible !
end
.
La compilation de ce code, incluant une création d'une instance de classe abstraite, provoque l'erreur suivante :
E2402 : Construction de l'instance de la classe abstraite 'TAbstractClass'.
Il n'est pas possible de déclarer une classe abstract sealed :
type
TAbstractClass= class
abstract
sealed
(TObject)
ChampExistant : integer
;
procedure
FaitqqChose;
end
;
Provoque l'erreur suivante :
E2383 : ABSTRACT et SEALED ne peuvent être utilisés ensemble.
XI. Syntaxe d'assistance de classe (Class Helper)▲
Texte issu de la documentation de DELPHI 2006 bêta
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 plateforme.
Leur création est due principalement aux problématiques de portage de la VCL sous .NET. Pour plus d'informations consultez le tutoriel Le langage Delphi pour .NET.
XI-A. Un possible exemple d'utilisation▲
Lors de la création d'une instance de classe dans un bloc With do begin end, on aimerait récupérer une référence sur cette instance transitoire pour la communiquer à un traitement. Si on peut accéder à ces membres, on ne peut malheureusement pas accéder à l'instance elle-même.
Sous Delphi 2005 et 2006 l'utilisation de Class Helper peut être une solution, bien que l'utilisation d'une variable locale soit la solution la plus triviale.
Tout compte fait une classe Delphi peut manipuler l'instance via le mot clé self, mais uniquement à l'intérieur d'une méthode. L'idée est donc d'ajouter une fonction membre qui renvoie self comme résultat.
Type
GetterClassHelper=Class
Helper For
TObject
Function
GetInstance:TObject;
end
;
function
GetterClassHelper.GetInstance:TObject;
begin
// Self fait référence à la variable en cours,
// et non à une possible instance de type GetterClassHelper.
Result:=Self
;
end
;
On choisi de modifier la classe TObject afin de bénéficier de cet ajout de méthodes sur toutes les classes ancêtres.
L'appel dans le bloc With-Do devenant :
begin
With
TMonObjet.Create do
begin
...
Traitement(GetInstance); // Récupére la référence
...
end
;
end
.
Voici un programme de test :
program
classhelper;
{$APPTYPE CONSOLE}
uses
SysUtils;
Type
TTest=Class
Nom: String
;
end
;
GetterClassHelper=Class
Helper FOR
TObject
Function
WriteClassName:ShortString;
Function
GetInstance:TObject;
end
;
Function
GetterClassHelper.WriteClassName:ShortString;
begin
// Self fait référence au type Test, et non à GetterClassHelper.
Result:=Self
.ClassName;
end
;
function
GetterClassHelper.GetInstance:TObject;
begin
// Self fait référence à la variable en cours,
// et non à une possible instance de type GetterClassHelper.
Result:=Self
;
end
;
Procedure
Visualise(AInstance :TObject; NomVaraible:String
);
begin
Writeln('Nom de la variable : '
,NomVaraible);
Writeln;
Writeln('Instance de la classe '
+ TTest(AInstance).WriteClassName+' dans la methode visualise : '
+TTest(AInstance).Nom);
Writeln;
end
;
Var
Premier, Second : TTest;
begin
Premier:=TTest.Create;
Premier.Nom:='Je reference l''instance Premier'
;
visualise(Premier,'Premier'
);
// Teste si on récupère bien une référence sur l'instance Premier
// équivaut à :
// Second:=Premier
Second:=TTest(Premier.GetInstance);
visualise(Second,'Second'
);
Readln;
Premier.Free;
Second:=Nil
;
// Objectif final sur une classe personnelle
With
TTest.Create do
begin
Nom:='Je reference l''instance transitoire dans le bloc With-do'
;
visualise(GetInstance,' ici elle n''a pas de nom'
);
Free;
end
;
Readln;
// Objectif final sur une classe de la VCL
With
TStringList.Create do
begin
visualise(GetInstance,' ici elle n''a pas de nom'
);
Free;
end
;
Readln;
end
.
Les dernières lignes nous montrent bien qu'un assistant de classe peut nous aider à compléter une hiérarchie de classe sans modifier le code source de la classe d'origine.
Cela peut être une solution pour compléter des classes pour lesquelles on ne dispose que des déclarations de la partie interface.
Dans notre exemple le code suivant ne pourra pas compiler, car Self n'a pas d'existence dans ce programme console :
With
TTest.Create do
begin
Nom:='Je reference l''instance transitoire dans le bloc With-do'
;
visualise(self
,' Dans notre cas self n''existe pas'
);
Free;
end
;
Si dans une méthode d'une classe on utilise le mot clé self dans le bloc With do begin end, self fera référence à l'instance courante de la classe comportant la méthode en cours d'exécution, et non à l'instance de l'objet créé à la volée dans le bloc with.
Merci à Sylvain james pour cette dernière remarque.
Les membres d'un assistant de classe peuvent être :
- des méthodes de classes ;
- des variables de classes ;
- des propriétés.
Mais un assistant de classe ne peut pas déclarer de champ supplémentaire ni de méthode virtual.
Il n'est pas possible de déclarer un assistant de classe d'un assistant de classe.
On peut déclarer plusieurs assistants de classe pour une même classe, mais il semble que seul le dernier déclaré soit pris en compte et annule les précédents.
TDescellee= class
Helper For
TSealedClass
class
var
unChampDeClass : integer
;
procedure
FaitqqChose;
Class
procedure
Affiche;
Constructor
Create;
end
;
Un assistant de classe peut être utilisé, comme le laisse supposer le dernier exemple, étendre une classe scellée (Sealed).
Notez que les assistants de classe ressemblent aux Extension Methods du C# 3.0.
XII. Les records▲
L'utilisation des directives StrictPrivate, Private et Public est désormais possible dans un enregistrement.
Afin d'aborder rapidement le sujet, déclarons dans une unité un type Record utilisant ces directives :
unit
TestRecord;
interface
Type
TUAdvancedRecord = record
Strict
private
Valeur_StrictPrivate : integer
;
Private
Valeur_Private : integer
;
Public
Valeur_Public: Integer
;
end
;
implementation
end
.
Il nous reste à mettre en évidence les règles de portée que ces directives permettent en utilisant le programme suivant :
program
NouveauRecord1;
{$APPTYPE CONSOLE}
uses
SysUtils,
TestRecord in
'TestRecord.pas'
;
type
TAdvancedRecord = record
Strict
private
Valeur_StrictPrivate : integer
;
Private
Valeur_Private : integer
;
Public
Valeur_Public: Integer
;
end
;
var
TypeRecordDansLaMemeUnite : TAdvancedRecord;
TypeRecordDansUniteDifferente :TUAdvancedRecord;
begin
with
TypeRecordDansLaMemeUnite do
begin
//Valeur_StrictPrivate:=1; // error E2003 : Identificateur non déclaré 'Valeur_StrictPrivate'
Valeur_Private:=2
;
Valeur_Public:=3
;
end
;
with
TypeRecordDansUniteDifferente do
begin
//Valeur_StrictPrivate:=1; // error E2003 : Identificateur non déclaré 'Valeur_StrictPrivate'
//Valeur_Private:=2; // error E2003 : Identificateur non déclaré 'Valeur_Private'
Valeur_Public:=3
;
end
;
end
.
Les membres déclarés avec la directive :
- Strict private sont accessibles uniquement par les membres du Record ;
- Private sont accessibles uniquement dans l'unité où le Record est déclaré ;
- Public sont accessibles dans tous le code.
XII-A. Les records ont désormais de la classe▲
Le type record est désormais considéré soit comme traditionnel, soit comme avancé.
La dénomination traditionnel concerne le type record qu'on connait depuis le Pascal, la dénomination avancée est un record ayant, sur certains points, un comportement plus proche d'une classe.
Déjà sous Delphi 2005 .NET le type record avait été modifié pour correspondre au principe qui veut que tout soit objet sous .NET. Leur existence sous Delphi 2005 Win32 n'ayant pas été documentée, c'est désormais chose faite.
Texte de la documentation de Delphi 2006 bêta
En plus des types record traditionnels, Delphi autorise des types record plus complexe et plus proche d'une
classe.
En plus des champs, les records peuvent avoir des propriétés et des méthodes (y compris des constructeurs),
des propriétés de classe, des méthodes de classe, des champs de classe, et des types imbriqués.
Voici une définition d'un type record simple avec certaines fonctionnalités de classes.
(note : le texte d'origine utilise l'expression anglaise 'class-like')
type
TMyRecord = record
type
TInnerColorType = Integer
;
var
Red: Integer
;
class
var
Blue: Integer
;
procedure
printRed();
constructor
Create(val: Integer
);
property
RedProperty: TInnerColorType read
Red write
Red;
class
property
BlueProp: TInnerColorType read
Blue write
Blue;
end
;
constructor
TMyRecord.Create(val: Integer
);
begin
Red := val;
end
;
procedure
TMyRecord.printRed;
begin
writeln('Red: '
, Red);
end
;
Cependant les records peuvent maintenant partager une grande partie des fonctionnalités des classes,
il y a quelques différences importantes entre les classes et les records.
- Les records ne supportent pas l'héritage.
- Les records peuvent contenir des parties variables, les classes ne le peuvent pas.
- Les records sont des types valeur, ainsi ils sont copiés lors d'affectation, passés par valeur,
et alloués sur la pile à moins qu'ils soient déclarés globalement ou explicitement alloués en utilisant
les fonctions New et Dispose. Les classes sont des types référence, elles ne sont donc pas copiées lors
d'une affectation, elles sont passées par référence, et elles sont allouées sur le tas (Heap).
- Les records permettent la surcharge d'opérateurs sur les plateformes Win32 et .NET; Les classes permettent la
surcharge d'opérateurs seulement sous .NET.
- Les records sont construits automatiquement, en utilisant un constructeur par défaut qui est sans argument,
mais les classes doivent être explicitement construites.
Puisque les records ont un constructeur par défaut, n'importe quel constructeur de record défini par
l'utilisateur doit avoir obligatoirement un ou plusieurs paramètres.
- Les types record ne peuvent pas avoir de destructeurs.
- Les méthodes virtuelles (celles indiquées avec les mots clés virtual, dynamic, et message) ne peuvent pas être
employées dans les types record.
- À la différence des classes, les types record sur la plateforme Win32 ne peuvent pas implémenter d'interfaces;
Cependant les records sur la plateforme .NET le peuvent.
Désormais les record comme les classes peuvent utiliser les déclarations de type imbriqué.
Vous trouverez un exemple plus avant dans le chapitre sur la surcharge d'opérateur.
XII-B. Surcharge d'opérateurs dans les enregistrements▲
Texte issu de la documentation de DELPHI 2006 bêta
Delphi .NET autorise la surcharge de certaines fonctions (ou « opérateurs ») dans les déclarations de classe et d'enregistrement. Le nom de la fonction opérateur correspond à une représentation symbolique dans le code source. Par exemple, l'opérateur Add correspond au symbole +. Le compilateur génère un appel à la surcharge appropriée, en faisant correspondre le contexte (c'est-à-dire le type de retour et le type des paramètres utilisés dans l'appel) à la signature de la fonction opérateur.
Le tableau suivant énumère les opérateurs Delphi pouvant être surchargés :
Opérateur |
Catégorie |
Signature de déclaration |
Mappage de symbole |
---|---|---|---|
Implicit |
Conversion |
Implicit(a : type): resultType; |
implicit typecast |
Explicit |
Conversion |
Explicit(a: type): resultType; |
explicit typecast |
Negative |
Unaire |
Negative(a: type): resultType; |
- |
Positive |
Unaire |
Positive(a: type): resultType; |
+ |
Inc |
Unaire |
Inc(a: type): resultType; |
Inc |
Dec |
Unaire |
Dec(a: type): resultType |
Dec |
LogicalNot |
Unaire |
LogicalNot(a: type): resultType; |
not |
BitwiseNot |
Unaire |
BitwiseNot(a: type): resultType; |
not |
Trunc |
Unaire |
Trunc(a: type): resultType; |
Trunc |
Round |
Unaire |
Round(a: type): resultType; |
Round |
Equal |
Comparaison |
Equal(a: type; b: type): Boolean; |
= |
NotEqual |
Comparaison |
NotEqual(a: type; b: type): Boolean; |
<> |
GreaterThan |
Comparaison |
GreaterThan(a: type; b: type) Boolean; |
> |
GreaterThanOrEqual |
Comparaison |
GreaterThanOrEqual(a: type; b: type): resultType; |
>= |
LessThan |
Comparaison |
LessThan(a: type; b: type): resultType; |
< |
LessThanOrEqual |
Comparaison |
LessThanOrEqual(a: type; b: type): resultType; |
<= |
Add |
Binaire |
Add(a: type; b: type): resultType; |
+ |
Subtrac |
Binaire |
Subtract(a: type; b: type): resultType; |
- |
Multiply |
Binaire |
Multiply(a: type; b: type): resultType; |
* |
Divide |
Binaire |
Divide(a: type; b: type): resultType; |
/ |
IntDivide |
Binaire |
IntDivide(a: type; b: type): resultType; |
div |
Modulus |
Binaire |
Modulus(a: type; b: type): resultType; |
mod |
ShiftLeft |
Binaire |
ShiftLeft(a: type; b: type): resultType; |
shl |
ShiftRight |
Binaire |
ShiftRight(a: type; b: type): resultType; |
shr |
LogicalAnd |
Binaire |
LogicalAnd(a: type; b: type): resultType; |
et |
LogicalOr |
Binaire |
LogicalOr(a: type; b: type): resultType; |
ou |
LogicalXor |
Binaire |
LogicalXor(a: type; b: type): resultType; |
xor |
BitwiseAnd |
Binaire |
BitwiseAnd(a: type; b: type): resultType; |
et |
BitwiseOr |
Binaire |
BitwiseOr(a: type; b: type): resultType; |
ou |
BitwiseXor |
Binaire |
BitwiseXor(a: type; b: type): resultType; |
xor |
Aucun autre opérateur ne peut être défini sur un enregistrement.
Les méthodes d'opérateurs surchargés ne peuvent pas être référencées par nom dans le code source :
y := x + x; // Appelle TMaClasse.Add(a, b: TMaClasse): TMaClasse
y := x add x ; // Interdit !
y := TMaClasse.add(x, x); // Interdit !
Pour accéder à une méthode d'opérateur spécifique d'une classe spécifique, vous devez utiliser des transtypages explicites sur tous les opérandes :
A,Z : integer
Begin
A:= Z+TMaClasse(x);
Il n'existe aucune hypothèse concernant les propriétés distributives ou commutatives de l'opération. Pour les opérateurs binaires, le premier paramètre est toujours l'opérande gauche et le second paramètre l'opérande droit. En l'absence de parenthèses explicites, l'associativité est supposée être de gauche à droite.
En règle générale, les opérateurs ne doivent pas modifier leurs opérandes. À la place, ils renvoient une nouvelle valeur, construite en effectuant l'opération sur les paramètres.
XII-C. Déclaration des surcharges d'opérateurs▲
Les surcharges d'opérateurs sont déclarées dans des enregistrements avec la syntaxe suivante :
type
typeName = [record
]
class
operator Signature_de_déclaration (a: type
): Type_résultant;
liste d'opérateur
end
;
L'implémentation des opérateurs surchargés doit aussi inclure la syntaxe class operator :
class
operator Name._de_type. Signature _de_déclaration (a: type
): Type_résultant;
Exemples d'opérateurs surchargés pour un enregistrement permettant de simuler un type :
unit
UTDBBool;
interface
type
TDBBool = record
Strict
private
// Champ privé pour stocker la valeur du type
FValeur : integer
;
// champs de classe
Class
var
FdbFalse : TDBBool;
Class
var
FdbNull : TDBBool;
Class
var
FdbTrue : TDBBool;
// Type interne et Privé
type
DBType = -1
..1
; // Etendue du type
Private
// Le constructeur ne doit pas être accessible
// Private pour TDBBool.dbTrue:=TDBBool.Create(0);
Constructor
Create(AValeur: DBType);
// Constantes internes et Privées
const
// Valeurs du type autorisé
// On ne reprend pas à l'identique les valeurs du type Boolean de Delphi
// Le zéro permet les tests x>0 pour true et x<0 pour false, voir aussi l'opérateur LogicalNot
cdbNull : integer
=0
;
cdbFalse : Integer
=-1
;
cdbTrue : Integer
=1
;
Class
procedure
Init;Static
;
Public
// Examine la valeur d'un DBBool.
// Renvoie true si la valeur de Fvaleur correspond à la valeur testée
Function
IsNull : Boolean
;
Function
IsFalse : Boolean
;
Function
IsTrue : Boolean
;
// Surcharge d'opérateur
Class
operator Implicit(Avalue: Boolean
): TDBBool;
Class
operator Implicit(x: TDBBool): String
;
Class
operator Explicit(x: TDBBool): Boolean
;
Class
operator Equal(x,y : TDBBool):TDBBool; // =
Class
operator NotEqual(x,y : TDBBool):TDBBool; // <>
Class
operator LogicalNot(x : TDBBool):TDBBool; // not
Class
operator LogicalAnd(x,y : TDBBool):TDBBool; // and
Class
operator LogicalOr(x,y : TDBBool):TDBBool; // or
// Convertit la valeur spécifiée en sa représentation String équivalente.
Function
ToString: String
;
// Propriétées en ReadOnly
Class
Property
dbFalse : TDBBool Read
FdbFalse;
Class
Property
dbNull : TDBBool Read
FdbNull;
Class
Property
dbTrue : TDBBool Read
FdbTrue;
end
;
implementation
uses
Classes;
Constructor
TDBBool.Create(AValeur: DBType);
Begin
FValeur:=AValeur;
end
;
Class
Procedure
TDBBool.Init;
Begin
TDBBool.FdbNull.FValeur:=cdbNull;
TDBBool.FdbFalse.FValeur:=cdbFalse;
TDBBool.FdbTrue.FValeur:=cdbTrue;
end
;
Function
TDBBool.IsNull : Boolean
;
begin
Result:=(Fvaleur=cdbNull);
end
;
Function
TDBBool.IsFalse : Boolean
;
begin
Result:=(Fvaleur=cdbFalse);
end
;
Function
TDBBool.IsTrue : Boolean
;
begin
Result:=(Fvaleur=cdbTrue);
end
;
// implémentation des Opérateurs
// Conversion implicite de boolean vers TDBBool.
// renvoie DBBool.FdbTrue pour true
// et DBBool.FdbFalse pour false
Class
operator TDBBool.Implicit(Avalue: Boolean
): TDBBool;
begin
If
Avalue=True
then
Result:=FdbTrue
else
Result:=FdbFalse;
end
;
// Conversion explicite de TDBBool vers boolean.
// Déclenche une exception si x est égal à cdbNull,
// sinon renvoie True ou False.
Class
operator TDBBool.Explicit(x: TDBBool): Boolean
;
begin
If
x.FValeur=cdbNull
then
raise
EInvalidOperation.Create('TDBOOL:opération invalide'
)
else
Result:= x.FValeur>cdbNull // x.FValeur=cdbTrue;
end
;
// Opérateur d'égalité. Renvoi FdbNull si l'un des opérandes est égal à cdbNull,
// sinon renvoie FdbTrue ou FdbFalse:
Class
operator TDBBool.Equal(x : TDBBool; y : TDBBool):TDBBool;
// ATTENTION : pas d'évaluation booléenne complète, cf C#
begin
if
((x.FValeur = cdbNull) or
(y.FValeur =cdbNull))
then
result:=FdbNull
else
if
x.FValeur = y.FValeur
then
Result:=FdbTrue
else
Result:=FdbFalse;
end
;
// Opérateur d'inégalité. Renvoie FdbNull si l'un des opérandes est égal à FdbNull,
// sinon renvoie FdbTrue ou FdbFalse:
Class
operator TDBBool.NotEqual(x,y : TDBBool):TDBBool;
// ATTENTION : pas d'évaluation booléenne complète, cf C#
Begin
if
((x.FValeur = cdbNull) or
(y.FValeur =cdbNull))
then
result:=FdbNull
else
if
x.FValeur <> y.FValeur
then
Result:=FdbTrue
else
Result:=FdbFalse;
end
;
// Opérateur de négation logique. Renvoie :
// FdbTrue si l'opérande est égale à FdbFalse
// FdbNull si l'opérande est égale à FdbNull,
// ou FdbFalse si l'opérande est égale à FdbTrue.
Class
operator TDBBool.LogicalNot(x : TDBBool):TDBBool;
begin
Result:=TDBBool.Create(-x.FValeur);
end
;
// Opérateur ET ( AND ) logique. Renvoie
// FdbFalse si l'un des opérandes est égal à FdbFalse,
// FdbNull si l'un des opérandes est égal à FdbNull,
// sinon renvoie FdbTrue.
Class
operator TDBBool.LogicalAnd(x,y : TDBBool):TDBBool;
var
Resultat:Integer
;
begin
If
x.FValeur < y.FValeur
then
Resultat:=x.FValeur
else
Resultat:=y.FValeur;
Result:=TDBBool.Create(Resultat);
end
;
// Opérateur ET ( AND ) logique. Renvoie
// FdbTrue si l'un des opérandes est égal à FdbTrue,
// FdbNull si l'un des opérandes est égal à FdbNull,
// sinon renvoie FdbFalse.
Class
operator TDBBool.LogicalOr(x,y : TDBBool):TDBBool;
var
Resultat:Integer
;
begin
If
x.FValeur > y.FValeur
then
Resultat:=x.FValeur
else
Resultat:=y.FValeur;
Result:=TDBBool.Create(Resultat);
end
;
// Conversion implicite de TDBBool vers String.
Class
operator TDBBool.Implicit(x: TDBBool): String
;
begin
If
x.Fvaleur > 0
then
Result:='dbTrue'
else
If
x.Fvaleur < 0
then
Result:='dbFalse'
else
Result:='dbNull'
;
end
;
// Surcharge la méthode ToString pour convertir un TDBBool vers une String.
Function
TDBBool.ToString: String
;
begin
Case
Fvaleur of
-1
: Result:='TDBBool.False'
;
0
: Result:='TDBBool.Null'
;
1
: Result:='TDBBool.True'
;
else
Raise
EInvalidOperation.Create('TDBOOL:opération invalide'
);
end
;
End
;
// Initialise les valeurs possibles du type
Initialization
TDBBool.Init;
end
.
Les constructeurs de classe n'existant pas sous Win32 on utilisera la partie Initialization d'une unité pour simuler son comportement.
Voici l'implémentation d'un opérateur surchargé, qui ne modifie par ses opérandes, mais renvoie une nouvelle valeur :
// Opérateur ET ( AND ) logique. Renvoie
// dbFalse si l'une des opérandes est égale à dbFalse,
// dbNull si l'un des opérandes est égal à dbNull,
// sinon renvoie dbTrue.
Class
operator TDBBool.LogicalAnd(x,y : TDBBool):TDBBool;
var
Resultat:Integer
;
begin
If
x.FValeur < y.FValeur
then
Resultat:=x.FValeur
else
Resultat:=y.FValeur;
Result:=TDBBool.Create(Resultat);
end
;
Le constructeur n'alloue pas de mémoire, mais accède aux champs de l'enregistrement courant. Le programme de test de cet enregistrement :
program
operateur;
{$APPTYPE CONSOLE}
uses
SysUtils,
UTDBBool in
'UTDBBool.pas'
,
classes;
var
a, b, c : TDBBool;
begin
//Initialisation des variables hors constructeur
a:=TDBBool.dbTrue;
// Assignation de la valeur de la variable a dans la variable b
b:=a;
b:=TDBBool.dbNull;
c:=TDBBool.dbFalse;
WriteLn('a = '
,a.ToString,' not a ='
, (not
a).ToString); // Appel : operator LogicalNot
WriteLn('c ='
, c.ToString); // Appel : operator LogicalOr
WriteLn('b = '
, b.ToString,' not b ='
, (not
b).ToString);
WriteLn('a and b ='
,(a and
b).ToString); // Appel : operator LogicalAnd
WriteLn('a or b ='
, (a or
b).ToString); // Appel : operator LogicalOr
Readln;
// l'instruction case n'est pas possible sur un type record
if
b=TDBBool.dbTrue then
Writeln('b est dbTrue'
)
else
if
b=TDBBool.dbFalse then
Writeln('b est dbFalse'
)
else
Writeln('b est dbNull'
);
// a=c renvoi un résultat erroné
// if boolean(a=c) renvoi un résultat erroné
if
boolean
(a=c)=true
then
Writeln('A = C'
);
//Valeur true ou false gérée
try
if
boolean
(a)=Boolean
(c) // Appel operator Explicit
then
Writeln('Boolean : a et c sont identiques'
)
else
Writeln('Boolean : a et c ne sont pas identiques'
);
except
On
E:EInvalidOperation do
WriteLn('a ou c est NULL'
)
end
;
//Valeur Null non gérée, mais opération valide
try
if
Boolean
(b)=True
then
WriteLn('b est true definitivement'
)
else
WriteLn('b n''est definitivement pas true '
);
except
On
E:EInvalidOperation do
WriteLn('b est NULL'
);
end
;
//Valeur Null non gérée, mais opération valide
try
if
boolean
(a)=Boolean
(b)
then
Writeln('a et b sont identiques'
)
else
Writeln('a et b ne sont pas identiques'
);
except
On
E:EInvalidOperation do
WriteLn('b est NULL'
)
end
;
Readln;
(* Résultats attendus :
not DBBool.True = DBBool.False
not DBBool.Null = DBBool.Null
DBBool.True & DBBool.Null = DBBool.Null
DBBool.True or DBBool.Null = DBBool.True
a et c ne sont pas identique
b est NULL
b est NULL
*)
end
.
Pour accéder à une méthode d'opérateur spécifique d'une classe spécifique, vous devez utiliser des transtypages explicites sur tous les opérandes : if Boolean(b)=True
XII-D. Les records dynamiques▲
L'utilisation des enregistrements dynamiques reste identique :
program
NouveauRecord2;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
PMyRec=^MyRec;
MyRec=Record
A,B :byte
;
Constructor
Create(lA,lB:byte
);
Procedure
Ecrit(Name:String
);
end
;
Constructor
MyRec.Create(lA,lB:byte
);
begin
A:=lA;
B:=lB;
end
;
Procedure
MyRec.Ecrit(Name:String
);
begin
Writeln(Name,' A='
,A,' B='
,B);
end
;
procedure
visu;
var
T: MyRec;
begin
// T n'est pas initialisé
T.Ecrit('T locale'
);
end
;
var
Un : MyRec;
R : PMyRec;
begin
//Variable globale initialisée à Zéro
Un.Ecrit('UN'
);
visu;
New(R); //Alloue une zone mémoire
R^.Create(1
,2
); //Le constructeur réaffecte la même zone mémoire
Un.Create(1
,2
); //Le constructeur réaffecte la même zone mémoire
Un:=MyRec.Create(10
,16
); //L'assignation réaffecte la même zone mémoire
Dispose(R); //Désalloue la zone mémoire
R:=Nil
;
R^.Ecrit('R après NIL.'
); //Violation d'accès
end
.
Attention les variables globales sont initialisées à zéro, dans tous les autres cas vous devez appeler le constructeur ou initialiser les champs avant de manipuler un enregistrement.
XIII. Directives de compilation▲
XIII-A. 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
UMonObjet platform
experimental;
XIII-B. Description▲
De portée globale, sa syntaxe est :
{$DESCRIPTION 'text'}
La directive $D insère le texte que vous avez spécifié dans l'entrée 'description de module' de l'entête d'un exécutable, d'une DLL, ou d'un package. Traditionnellement le texte est un nom, un numéro de version, une note de copyright, mais vous pouvez spécifier n'importe quel texte de votre choix.
Par exemple:
{$D 'Mon Application version 12.5'}
La chaine de caractères ne doit pas dépasser 256 octets. La description n'est habituellement pas visible par l'utilisateur final. Pour marquer votre fichier exécutable avec du texte descriptif, un numéro de version et des informations de copyright à l'usage des utilisateurs finaux, utiliser les ressources d'information de version.
Note :
Le texte doit être inclus entre quotes.
XIII-C. Extension de l'exécutable▲
Déjà présent sous Delphi 2005, sa syntaxe est :
{$E extension}
ou {$EXTENSION extension}
La directive $E définit l'extension du fichier exécutable généré par le compilateur. Elle est souvent utilisée avec le mécanisme DLL ressource seulement. Par exemple, placer {$E deu} dans un module bibliothèque génère une DLL ayant l'extension .deu : nomfichier.deu. Si vous créez un module bibliothèque qui ne référence que des chaines et des fiches allemandes, vous pouvez utiliser cette directive pour produire une DLL ayant l'extension .deu.
Le code de démarrage de la bibliothèque d'exécution cherche une DLL dont l'extension correspond à la locale du système — pour l'allemand, il cherche .deu — et charge les ressources de cette DLL.
XIII-D. Directives de compilation pour les bibliothèques ou les objets partagés▲
Déjà présent sous Delphi 2005 et de portée globale, sa syntaxe est :
$LIBPREFIX
'chaine'
$LIBSUFFIX
'chaine'
$LIBVERSION
'chaine'
//exemple
$LIBPREFIX
'lib'
ou $LIBPREFIX
'bpl'
$LIBSUFFIX
' '
$LIBVERSION
' '
$LIBPREFIX remplace le préfixe 'lib' ou 'bpl' par défaut dans le nom du fichier de destination.
Par exemple, vous pouvez spécifier {$LIBPREFIX 'dcl'} pour un package de conception, ou utiliser la directive suivante pour éliminer le préfixe entièrement :
{$LIBPREFIX ' '}
{$LIBSUFFIX ' '} ajoute le suffixe spécifié au nom du fichier de destination, avant l'extension. Par exemple, utilisez :
{$LIBSUFFIX '-2.1.3'}
dans quelquechose.pas pour générer : quelquechose-2.1.3.dll
$LIBVERSION ajoute une seconde extension au nom du fichier de destination après l'extension. Par exemple, utilisez
{$LIBVERSION '-2.1.3'}
dans quelquechose.pas pour générer : libquelquechose.dll.2.1.3
XIII-E. Vérification des exceptions sur les flottants (Floating Point Exception Checking)▲
Switch de portée globale, sa syntaxe est
{$FINITEFLOAT ON}
, {$FINITEFLOAT OFF}
La directive $FINITEFLOAT contrôle les débordements sur les flottants (overflow et underflow), et les opérations invalides telles que la division par zéro.
Avec {$FINITEFLOAT ON}, qui est l'état par défaut, les résultats des calculs sur les flottants sont vérifiés, et une exception est déclenchée lorsqu'il y a un dépassement de capacités (overflow et underflow), ou une opération invalide.
Avec {$FINITEFLOAT OFF}, de tels calculs sur des flottants renverront NAN, -INF ou +INF.
Un temps de traitement supplémentaire est nécessaire pour vérifier les résultats des calculs sur des nombres à virgule flottante et pour déclencher des exceptions.
Si votre code Delphi utilise des opérations de virgule flottante, mais n'exige pas l'application stricte des exceptions de dépassement de capacités (overflow et underflow), vous pouvez utiliser {$FINITEFLOAT OFF} pour obtenir une exécution légèrement plus rapide.
Note :
La majorité du code sous .NET fonctionne sans contrôles de virgule flottante. Cependant, Delphi a traditionnellement fourni la sémantique stricte des nombres à virgule flottante.
Si vous avez du code Delphi qui se base sur des exceptions de dépassement de capacités (overflow et underflow), vous devriez maintenir la directive par défaut ({$FINITEFLOAT ON}).
XIII-F. Informations de méthode▲
Switch de portée locale, sa syntaxe est
{$METHODINFO ON}
or
{$METHODINFO OFF}
Par défaut à OFF.
La directive $METHODINFO est effective seulement lorsque les informations RTTI (runtime type information) ont été autorisées avec la directive {$TYPEINFO ON}.
Dans l'état {$TYPEINFO ON}, La directive $METHODINFO contrôle la génération de descripteurs de méthode plus détaillés dans le RTTI pour des méthodes dans une interface.
Bien que {$TYPEINFO ON} générera des informations RTTI pour des méthodes publiées, le niveau de l'information est limité. La directive $METHODINFO produit des informations RTTI beaucoup plus détaillées (et d'un nombre plus important) pour des méthodes, qui décrit comment les paramètres de la méthode devraient être passés sur la pile et/ou dans des registres.
Il est rare qu'une application utilise directement la directive $METHODINFO.
Les informations de méthodes augmentent considérablement la taille du fichier exécutable, et son usage n'est pas recommandé pour une utilisation courante.
Note
Le support du code web service du compilateur Delphi Win32 utilise les informations de descripteurs de méthodes afin de passer des paramètres reçus dans une trame réseau (network packet) à la méthode cible. {$METHODINFO ON} est employé seulement pour des types d'interface web service.
Voir la directive {$TYPEINFO }.
XIII-G. Champs d'entête PE (portable executable)▲
Flag de portée local, sa syntaxe est :
{$SetPEFlags <integer expression>}
{$SetPEOptFlags <integer expression>}
Microsoft s'appuie sur les drapeaux d'entête PE (portable executable) pour permettre à une application d'indiquer la compatibilité avec des services de l'OS ou de demander des services avancés de l'OS.
Ces directives fournissent des options puissantes pour optimiser vos applications sur les systèmes 'high-end' NT.
Il n'y a aucune vérification des erreurs ou des masques de bit indiquée par ces directives. Si vous placez une mauvaise combinaison, vous pouvez corrompre votre fichier exécutable.
Ces directives vous permettent respectivement de positionner un ensemble de flags dans le champ Characteristics et dans le champ facultatif DLLCharacteristics d'un entête de fichier PE. La plupart des flags Characteristics, ensemble utilisant $SetPEFlags, sont spécifiques aux fichiers et aux librairies. Les flags DLLCharacteristics, ensemble utilisant $SetPEOptFlags, décrivent quand appeler le point d'entrée d'une DLL. La partie '<integer expression> dans ces directives peut inclure des identifiants de constantes Delphi, telles que les constantes IMAGE_FILE_xxxx définies dans Windows.pas.
Les constantes multiples doivent être un ensemble construit avec l'opérateur OR.
Vous pouvez inclure plusieurs fois ces directives dans le code source. Les valeurs des flags indiquées par des directives multiples sont strictement cumulatives :
si la première occurrence de la directive affecte $03 et la deuxième occurrence affecte $10, la valeur écrite dans le fichier exécutable au moment de l'édition de lien sera $13 (plus quelque autres que l'éditeur de liens place normalement dans les flags PE).
Ces directives affectent seulement le fichier de sortie (output file) s'il est inclus dans le code source avant l'édition des liens. Ceci signifie que vous devriez placer ces directives dans un fichier .dpr ou de .dpk, mais pas dans un fichier d'unité. Comme la directive de description de l'exe, ce n'est pas une erreur de placer ces directives dans du code source d'une unité, mais ces directives dans le code source d'une unité n'affecteront pas le fichier de sortie (exe ou DLL) à moins que le source de l'unité soit recompilé lorsque le fichier de sortie est linké.
XIV. Conclusions▲
Ces évolutions, certaines déjà présentent dans Delphi 2005, faciliteront, entre autres, le développement de code partagé sous Win32 et .NET en évitant le recours massif aux directives de compilation conditionnelle.
Pour ma part j'aurais apprécié pour les Record dynamique une syntaxe facilitant l'allocation et l'appel du constructeur en une passe, un peu comme sous TP 7.0 :-), ainsi qu'une instruction similaire au FreeAndNil pour la désallocation.
Quoi qu'il en soit ces nouveautés du langage sont déjà fort appréciables et il me tarde de découvrir celle de Highlander qui s'annonce révolutionnaire pour Delphi, mais chaque chose en son temps ;-).