Les possibles évolutions du langage Delphi sous Delphi 2005

Laurent Dardenne

Pour une présentation de Delphi 2005, vous pouvez consulter l'article de Pierre Castelain.

Cet article présente les évolutions du langage Delphi sous Delphi 2005.

Il s'agit bien sûr de possibles évolutions étant donné qu'à ce jour, début octobre 2004, le produit n'est pas encore finalisé.

Sous réserve de ma traduction de l'aide en ligne de Delphi 2005. ♪

- Titre : Les possibles évolutions du langage Delphi sous Delphi 2005

- Auteur : Laurent Dardenne

- Parution : 9 octobre 2007

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2013 Laurent Dardenne. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Convention d'appel INLINE

- II. Déclarations imbriquées

- II-A. Déclaration de types imbriqués

- II-A-1. Déclarer et accéder aux classes imbriquées

- II-B. Déclarations de constantes imbriquées

- III. Itération sur des conteneurs et des classes

- III-A. Rappel de l'instruction For

- III-B. Itération sur des conteneurs

- III-C. Itération sur des classes

- IV. Les directives de compilations

- IV-A. MESSAGE

- IV-B. REALCOMPATIBILITY

- IV-C. UNSAFE

- IV-D. NODEFINE

- IV-E. NOINCLUDE

I - Convention d'appel INLINE

Dans la déclaration d'une procédure ou d'une fonction, vous pouvez spécifier la nouvelle directive INLINE qui permet d'améliorer l'exécution des fonctions et des procédures. Si la fonction ou la procédure répond à certains critères, le compilateur insérera le code directement, plutôt que de produire un appel (Call xxxx).

Elle permet d'obtenir un code plus rapide par une optimisation de l'exécution, mais aux dépens de l'espace mémoire. Cette directive dupliquant le code à chaque fois. Son comportement est proche des macro-instructions disponibles dans les langages assembleurs (elle existait déjà sous TP6 & 7). La directive INLINE générera toujours un fichier binaire de taille plus importante. Elle est employée comme d'autres directives telles que cdecl ou safecall.

Par exemple :

procedure MyProc(x:Integer); inline;

begin

// ...

end;

function MyFunc(y:Char) : String; inline;

begin

// ..

end;

La directive inline est une suggestion faite au compilateur. Il n'y a aucune garantie que le compilateur duplique le code d'une routine particulière, car il existe certaines circonstances où cette directive ne peut pas être utilisée (+ de 10 cas recensés), par exemple :

- les routines contenant de l'assembleur ;

- les constructeurs et les destructeurs ;

- le bloc de programme principal, d'initialisation et de finalisation d'unité ;

- les routines qui utilisent des tableaux ouverts comme paramètres ;

- etc.

Si vous modifiez l'implémentation d'une routine INLINE, vous provoquerez une recompilation de toutes les unités qui l'utilisent. C'est une modification des règles traditionnelles de reconstruction qui sont déclenchées seulement par des changements de la section d'interface d'une unité. La directive de compilation {$INLINE [ON(default), OFF, AUTO]} vous offrira un contrôle plus fin des différents cas de figure.

II - Déclarations imbriquées

II-A - Déclaration de types imbriqués

Des déclarations de types peuvent être imbriquées dans une déclaration de classe. Les types imbriqués sont employés dans tout le framework .NET, et généralement dans toute Programmation Orientée Objet. Ce type de déclaration présente l'intérêt de regrouper les types « conceptuellement associés », et d'éviter des collisions de noms. La même syntaxe peut être désormais employée avec le compilateur Win32 de Delphi 2005.

Exemple de déclaration de types imbriqués :

type

className = class [abstract | sealed] (ancestorType)

memberList

type

DeclarationDeTypeImbriqué

memberList

end;

Là où DeclarationDeTypeImbriqué suit la syntaxe de déclaration de type définie d'ordinaire dans la partie de déclaration nommée Type.

Les déclarations de types imbriqués prennent fin dès la première occurrence d'un élément du langage autre qu'un identifieur, par exemple, procedure , class, type, ou tous les spécificateurs de portée.

Les règles normales d'accès s'appliquent aux types imbriqués et à leurs types contenants, c'est-à-dire de niveau supérieur que j'appellerai classe conteneur.

Un type imbriqué peut accéder à une variable d'instance (champ, propriété, ou méthode) de sa classe conteneur, mais il doit utiliser une référence d'objet pour le faire. Un type imbriqué peut accéder à des champs de classe, des propriétés de classe et des méthodes statiques de la classe sans référence d'objet, mais dans ce cas les règles normales de visibilité de Delphi s'appliquent.

Les types imbriqués n'augmentent pas la taille de la classe conteneur. En d'autres termes, créer une instance de la classe conteneur ne crée pas par la même occasion une instance du type imbriqué. Les types imbriqués sont associés à leurs classes conteneurs uniquement par le contexte de leur déclaration.

II-A-1 - Déclarer et accéder aux classes imbriquées

Les exemples suivants vous indiquent comment déclarer et accéder à des champs et à des méthodes de classes imbriquées.

type

TClassConteneur = class

strict private

myField: Integer;

public

type     // Début de déclaration de la classe imbriquée

TClassImbriquee = class

public

monChampImbrique: Integer;

procedure ProcImbrique;

end; // Fin de déclaration de la classe imbriquée

procedure ProcConteneur; // Ici prend fin de déclaration du type imbriqué

end;

Pour appliquer la méthode ProcImbrique d'une classe imbriquée, vous devez qualifier son nom avec le nom de la classe conteneur.

Par exemple :

procedure TClassConteneur.TClassImbriquee.innerProc;

begin

...

end;

Pour accéder aux membres du type imbriqué, employez la notation habituelle (notation pointée : Classe.Membre). Par exemple :

var

x: TClassConteneur;

y: TClassConteneur.TClassImbriquee;

begin

x := TClassConteneur.Create;

x.ProcConteneur;

...

y := TClassConteneur.TClassImbriquee.Create;

y.ProcImbrique;

II-B - Déclarations de constantes imbriquées

Des constantes peuvent être déclarées dans des types de classe de la même manière que la section type imbriqué.

Les sections constantes sont terminées de la même manière que les sections de type imbriquées.

Les constantes typées ne sont pas supportées, vous ne pouvez donc pas déclarer des constantes imbriquées telles que Currency, ou TdateTime.

Les constantes imbriquées peuvent être de n'importe quel type simple : ordinaux et sous-ensemble ordinaux, énumération, String et Real.

Exemple de code de déclaration de constantes imbriquées :

type

TMyClass = class

const

x = 12;

y = TMyClass.x + 23;

procedure Hello;

private

const

s = 'A string constant';

end;

begin

writeln(TMyClass.y);   // Writes the value of y, 35.

end.

III - Itération sur des conteneurs et des classes

III-A - Rappel de l'instruction For

Une instruction For, à la différence d'une instruction Repeat ou While, exige que vous indiquiez explicitement le nombre d'itérations souhaitées. La syntaxe d'une instruction FOR est soit :

for compteur := Valeurinitiale to ValeurFinale do instructions

Soit :

for compteur := Valeurinitiale downto ValeurFinale do instructions

Où :

- Compteur est une variable locale (déclarée dans le bloc contenant l'instruction for) de type ordinal, sans aucun qualificateur ;

- ValeurInitiale et ValeurFinale sont des expressions compatibles avec une assignation de compteur (type scalaire) ;

- Instructions est une instruction simple ou structurée qui ne modifie pas la valeur du compteur.

III-B - Itération sur des conteneurs

Delphi 2005 supporte, pour .NET et Win32, l'itération sur des éléments d'une collection (iteration over containers). Les itérations de conteneur suivantes sont reconnues par le compilateur :

- for Element in ArrayExpression do Instruction ;

- for Element in StringExpression do Instruction ;

- for Element in SetExpression do Instruction ;

- for Element in CollectionExpression do Instruction.

Le type de la variable d'itération doit correspondre au type contenu dans le conteneur. Pour chaque itération de la boucle, la variable d'itération pointe sur le membre courant de la collection. Comme avec les boucles classiques, la variable d'itération doit être déclarée dans le même bloc que pour l'instruction.

La variable d'itération ne peut pas être modifiée dans la boucle. Ceci inclut l'assignation et passage de paramètres à un paramètre de var d'une procédure. Procéder ainsi aura comme conséquence une erreur lors de la compilation.

Les expressions de tableau peuvent être du type simple ou multidimensionnel, de longueur fixe, ou dynamique. Le tableau est parcouru dans l'ordre croissant, commençant à partir de l'indice de limite inférieure et finissant à l'indice max du tableau.

L'exemple de code ci-dessous parcourt les différents types de tableaux :

type

TIntArray        = array[0..9] of Integer;

TGenericIntArray = array of Integer;

var

IArray1: array[0..9] of Integer   = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

IArray2: array[1..10] of Integer  = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

IArray3: array[1..2] of TIntArray = ((11, 12, 13, 14, 15, 16, 17, 18, 19, 20),

(21, 22, 23, 24, 25, 26, 27, 28, 29, 30));

MultiDimTemp: TIntArray;

IDynArray:    TGenericIntArray;

I: Integer;

begin

for I in IArray1 do

begin

// Fait qq chose avec I...

end;

// L'indexation commence à 1, la limite de rangée inférieure.

for I in IArray2 do

begin

// Fait qq chose avec I...

end;

// Itération sur un tableau multidimensionnel

for MultiDimTemp in IArray3 do   // indexation 1..2

for I in MultiDimTemp do       // indexation 0..9

begin

// Fait qq chose avec I...

end;

// Itération sur un tableau dynamique

IDynArray := IArray1;

for I in IDynArray do

begin

// Fait qq chose avec I...

end;

Le code suivant concerne l'itération sur des expressions de type chaîne (String) :

var

C: Char;

S1, S2: String;

Counter: Integer;

OS1, OS2: ShortString;

AC: AnsiChar;

begin

S1 := 'Now is the time for all good men to come to the aid of their country.';

S2 := '';

for C in S1 do

S2 := S2 + C;

if S1 = S2 then

WriteLn('SUCCESS #1');

else

WriteLn('FAIL #1');

OS1 := 'When in the course of human events it becomes necessary to dissolve...';

OS2 := '';

for AC in OS1 do

OS2 := OS2 + AC;

if OS1 = OS2 then

WriteLn('SUCCESS #2');

else

WriteLn('FAIL #2');

end.

Le code suivant concerne l'itération sur des expressions de type ensemble (Set) :

type

TMyThing = (one, two, three);

TMySet   = set of TMyThing;

TCharSet = set of Char;

var

MySet:   TMySet;

MyThing: TMyThing;

CharSet: TCharSet;

{$IF DEFINED(CLR)} // Indique si on compile pour DOT NET

C: AnsiChar;

{$ELSE}

C: Char;

{$IFEND}

begin

MySet := [one, two, three];

for MyThing in MySet do

begin

// Fait qq chose avec MyThing...

end;

CharSet := [#0..#255];

for C in CharSet do

begin

// Fait qq chose avec C...

end;

end.

III-C - Itération sur des classes

Pour utiliser la construction de boucle for-in sur une classe, la classe doit implémenter un pattern de collection imposé (prescribed collection pattern). Un type qui implémente le pattern de collection doit avoir les attributs suivants :

- la classe doit posséder une méthode publique d'instance appelée GetEnumerator(). La méthode GetEnumerator() doit renvoyer une classe, une interface, ou un type record ;

- la classe, l'interface, ou le record renvoyé par GetEnumerator() doit contenir une méthode d'instance publique appelée MoveNext(). La méthode MoveNext() doit renvoyer un booléen ;

- la classe, l'interface, ou le record renvoyé par GetEnumerator() doit contenir une propriété publique en lecture seule appelée Current. Le type de la propriété Current doit être du même type que celui contenu dans la collection.

Si le type de l'énumérateur retourné par la méthode GetEnumerator() implémente l'interface IDisposable, le compilateur appellera la méthode Dispose du type lorsque la boucle se terminera.

Le code suivant concerne l'itération sur un conteneur « énumérable » sous Delphi 2005.

type

TMyIntArray = array of Integer;

TMyEnumerator = class

Values: TMyIntArray;

Indice:  Integer;

public

constructor Create;

function GetCurrent: Integer;

function MoveNext:   Boolean;

property Current:    Integer read GetCurrent;

end;

TMyContainer  = class

public

function GetEnumerator: TMyEnumerator;

end;

constructor TMyEnumerator.Create;

begin

inherited Create;

Values := TMyIntArray.Create(100, 200, 300);

Indice := -1;

end;

function TMyEnumerator.MoveNext: Boolean;

begin

if Indice < High(Values) then

begin

Inc(Indice);

Result := True;

end

else

Result := False;

end;

function TMyEnumerator.GetCurrent: Integer;

begin

Result := Values[Indice];

end;

function TMyContainer.GetEnumerator: TMyThing;

begin

Result := TMyEnumerator.Create;

end;

var

MyContainer: TMyContainer;

I: Integer;

Counter: Integer;

begin

MyContainer := TMyContainer.Create;

Counter := 0;

for I in MyContainer do

Inc(Counter, I);

WriteLn('Counter = ', Counter);

end.

Les classes suivantes et leurs descendantes supportent la syntaxe For-in :

- TList ;

- TCollection ;

- TStrings ;

- TInterfaceList ;

- TComponent ;

- TMenuItem ;

- TCustomActionList ;

- TFields ;

- TListItems ;

- TTreeNodes ;

- TtoolBar.

IV - Les directives de compilations

IV-A - MESSAGE

Syntaxe: {$MESSAGE HINT|WARN|ERROR|FATAL 'texte' }
La directive MESSAGE permet au code source d'émettre des conseils, des avertissements, et des erreurs comme le fait le compilateur. C'est une directive semblable à #emit ou pragma warn du C et C++. Le type de message (HINT, WARN, ERROR, ou FATAL) est optionnel. Si aucun type de message n'est indiqué, celui par défaut est HINT. Le texte doit être délimité par de simples quotes.

Exemples :

{$MESSAGE 'Boo!'}                   émet un conseil (hint)

{$Message Hint 'Feed the cats'}     émet un conseil (hint)

{$messaGe Warn 'Looks like rain.'}  émet un avertissement ( warning )

{$Message Error 'Not implemented'}  émet une erreur,la compilation continue

{$Message Fatal 'Bang.  Yer dead.'} émits une erreur,la compilation prend fin

IV-B - REALCOMPATIBILITY

Notez que l'utilisation du type Real48 n'est plus recommandé (deprecated) sous .NET. Préférez dans la plupart des cas le type double.

IV-C - UNSAFE

Syntaxe :

{$UNSAFECODE ON} ou {$UNSAFECODE OFF}

Spécifique à la plateforme Dot Net.

La directive UNSAFECODE indique si le mot-clé unsafe est accepté par le compilateur. Avec {$UNSAFECODE ON}, vous pouvez identifier des procédures et des fonctions avec le mot-clé unsafe, par exemple :

procedure unsafeProc; unsafe;

begin

end;

Cette directive est utile seulement en .NET (en Win32 est-elle ignorée). Unsafe signifie que le code sera compilé en non managé.

IV-D - NODEFINE

Syntaxe :

{$NODEFINE Identifieur}

La directive NODEFINE empêche le symbole indiqué d'être inclus dans le fichier d'entête généré pour le C++, tout en permettant la génération d'information dans le fichier OBJ.

Quand vous employez NODEFINE, il est de votre responsabilité de définir n'importe quels types nécessaires avec HPPEMIT.

Par exemple :

type

Temperature = type double;

{$NODEFINE Temperature}

{$HPPEMIT 'typedef double Temperature'}

IV-E - NOINCLUDE

Syntaxe :

{$NOINCLUDE NomDeFichier}

La directive NOINCLUDE empêche le fichier indiqué d'être inclus dans les fichiers d'entête produits pour le C++.

Par exemple, {#$$NOINCLUDE>Unit1} enlève #include Unit1.