1. Public concerné

Image non disponible

Testé sous le framework .NET 1.1 et Delphi 2005 + update 3.
Version 1.0

Ce tutoriel contient quelques exemples de code C#, vous trouverez les bases de la conversion du C# vers Delphi .NET ici.

1-1. Les sources

Les fichiers sources des différents exemples :
FTP.
HTTP.

L'article au format PDF.

2. Les bases

Vous trouverez dans le tutoriel Les tableaux dynamiques, les bases sur la gestion des tableaux sous Pascal/Delphi.

Un tableau est un type structuré qui permet de mémoriser plusieurs valeurs dans une seule structure, sous Delphi .NET les tableaux statiques et dynamiques sont implémentés en utilisant le type System.Array du framework.

Extrait de l'aide du framework concernant la définition de la classe System.Array:

 
Sélectionnez

La classe Array est la classe de base pour les implémentations du langage qui prennent en charge 
les tableaux. Cependant, seuls les compilateurs et le système peuvent dériver explicitement de la 
classe Array. 
Les utilisateurs doivent employer les constructions de tableaux fournies par le langage.

L'allocation mémoire de tableaux dynamiques, comme indiqué dans ce chapitre du tutoriel déjà cité, est désormais impossible, ceci étant due à une gestion mémoire différente sous .NET. Vous trouverez quelques informations sur le sujet en consultant l'aide en ligne locale de Delphi 2005.

Consultez le site de Microsoft pour le détail de la classe System.Array. Les chapitres suivant reprennent par l'exemple l'utilisation des méthodes les plus courantes.

2-1. Du nouveau

Le framework distingue 2 types de tableaux :

  • les tableaux SZ, unidimensionnel et débutant à un indice zéro
  • et les autres, c'est à dire :
    • les tableaux unidimensionnel mais débutant à un indice différent de zéro (un indice négatif est aussi possible),
    • les tableaux multidimensionnels,
    • les tableaux de tableaux réguliers,
    • et les jagged array ou ragged array qui sont des tableaux de tableaux irréguliers c'est-a-dire qui n'ont pas le même nombre d'éléments dans toutes les directions.

Note : Un tableau de tableaux contient des pointeurs sur d'autres tableaux alors qu'un tableau multidimensionnel n'en contient pas.

Image non disponible
Tableau rectangulaire[ 6, 10 ]
Image non disponible
Tableau de tableaux référençant quatre tableaux horizontaux

Notez que toutes les dimensions d'un tableau multidimensionnel seront de la même taille. Mais dans un tableau jagged Array, il est possible de référencer des tableaux de différentes tailles.

Sous .NET le type tableau SZ (unidimensionnel) débute à zéro et est optimisé. Il est recommandé de privilégier les tableaux débutant avec un index zéro notamment pour faciliter la portabilité. Il faut savoir que les tableaux de tableaux ne sont pas compatibles avec la CLS (SDK 1.1) car il ne permet pas qu'un objet System.Array soit un élément de tableau.

La zone mémoire allouée pour un tableau contient des informations supplémentaires qui sont :

  • le nombre de dimensions (Rank),
  • la borne inférieure du tableau (LowerBound),
  • la longueur de chaque dimension (GetLength), i.e le nombre d'éléments contenus dans la dimension spécifiée.

2-2. Contrôle des indices hors limites

Le CLR propose nativement le contrôle d'accès des indices hors limites, la directive de Vérification des limites {$R+} ou {$RANGECHECKS ON} semble donc redondante sous Delphi .NET et ce pour les tableaux uniquement.

Projet : ..\Tableaux\Range_Error

 
Sélectionnez

Const 
 Nbitem=9;

type
 TArrayStatic = Array[0..Nbitem] of Byte;

var
  TableauStatic:TArrayStatic;
  
begin
 {$R-}
  I:=10;
  try
   TableauStatic[I]:=0;
  except
   On E:ERangeError do   // Est déclenchée avec R+ mais pas avec R-
    Writeln('Avec R+ : ERangeError '+E.Message);
   On E:System.IndexOutOfRangeException do // Est déclenchée avec R- mais pas avec R+
    Writeln('Avec R- : System.IndexOutOfRangeException '+E.Message);
  end;

Cette remarque ne concerne pas les chaînes de caractères, type System.String, bien que ce type soit implémenté comme un tableau de caractères.

 
Sélectionnez

 {$R-}
  Chaine:='1234';
  Caractere:=Chaine[5]; // Déclenche System.IndexOutOfRangeException avec R+ ou R-
  Chaine[5]:='A'; // Déclenche System.ArgumentOutOfRangeException avec R+ ou R-
  Writeln(Caractere);
  Writeln(Chaine);

Le code déclaré unsafe n'effectue pas ce contrôle.

3. Les tableaux SZ statiques et dynamiques

Delphi propose 2 types de tableau :

  • les tableaux statiques; Ils ne sont jamais à Nil.
  • les tableaux dynamiques; Ils peuvent être à Nil.

La différence concerne la définition de la taille dans la déclaration du type. Celle d'un tableau dynamique pouvant évoluer au grès des besoins pendant l'exécution du programme, les tableaux dynamiques n'ont donc pas de taille ni de longueur fixe.
Un tableau dynamique est managé par le runtime Delphi .Net et offre syntaxiquement plus de possibilité qu'un tableau en code natif .NET.

3-1. Création de tableau

Sous .NET une des évolutions concerne l'allocation dynamique de tableau par l'utilisation du mot clé NEW.

Voici un exemple d'utilisation de cette instruction :
Projet : ..\Tableaux\LesBases1

 
Sélectionnez

type
  TArrayDynamiqueSimple = Array of Byte;

var
  TableauDynamique: TArrayDynamiqueSimple;

begin
   // Alloue 256 octets. Array[0..255]
  TableauDynamique := New(TArrayDynamiqueSimple, 256);
end.

Ici l'instruction NEW crée un tableau unidimensionnel basé zéro et initialise toutes les valeurs à zéro.

La construction interne d'un tableau statique est identique.

Projet : ..\Tableaux\LesBases11

 
Sélectionnez

Const Nbitem=9;
type
  TArrayStatic = Array[0..Nbitem] of Byte;

var
  TableauStatic:TArrayStatic;

begin
  Writeln('TArrayStatic');
  ShowInfoTableau(TableauStatic); // Appel implicite au code IL <b>newarr</b>
  Writeln;
end.

Ici c'est le compilateur qui prend en charge la création (l'allocation mémoire) du tableau lors de l'initialisation du programme, c'est à dire avant l'instruction Writeln('TArrayStatic').

4. L'assignation de tableau

Projet : ..\Tableaux\LesBases2

Si on fusionne le code des exemples précédents (LesBases1 et LesBases11), l'instruction d'affectation suivante est considérée par le compilateur comme une recopie de tableau :

 
Sélectionnez

Const Nbitem=9;

type
 TArrayStatic = Array[0..Nbitem] of Byte;
 TArrayDynamiqueSimple = Array of Byte;

var
  TableauStatic:TArrayStatic;
  TableauDynamique: TArrayDynamiqueSimple;

begin
  TableauDynamique := New(TArrayDynamiqueSimple, 15);
  // Identique à
  //  TableauStatic:=New(TArrayDynamiqueSimple,15);
  TableauStatic:=TableauDynamique; // Recopie de tableau
end.

Pour ces 2 déclarations de type similaire le compilateur génère, lors de l'affectation TableauStatic:=TableauDynamique, un appel à System.Array.Copy(sourceArray,destinationArray,longueur).

Ici cette recopie est nécessaire car la variable TableauStatic est un tableau de type statique. Un tableau statique n'étant pas redimensionnable le compilateur doit donc créer une nouvelle référence de tableau, de la taille de TableauDynamique, puis y recopier le contenu de TableauDynamique et enfin affecter en interne la référence du nouveau tableau à TableauStatic. L'ancienne référence étant prise en charge par le Garbage Collector.

Voyons maintenant l'affectation de 2 tableaux dynamique.

Projet : ..\Tableaux\LesBases21

 
Sélectionnez

type
 TArrayDynamiqueSimple = Array of Byte;
 
var 
  Tb1,Tb2 : TArrayDynamiqueSimple;
begin
  ...
  Tb1:= New(TArrayDynamiqueSimple, 4);
  Tb2:= New(TArrayDynamiqueSimple, 7);
  Tb1:=Tb2;

Dans ce cas l'appel à TObject.equals(Tb1,Tb2) renvoie True. La variable Tb1 étant de type tableau dynamique, le compilateur lui affecte la référence de la variable Tb2, ces deux variables référencent désormais la même zone mémoire.

Pour obtenir deux références différentes de tableaux alloués dynamiquement mais de contenu identique on utilisera la méthode Clone.

Projet : ..\Tableaux\LesBases22

 
Sélectionnez

type
 TArrayDynamiqueObjet = Array of TObject;
 TArrayDynFileStream= Array of System.IO.FileStream;
var 
  TabObjet,TFS:  system.Array; 
begin
   TabObjet:=TArrayDynFileStream(TFS.Clone); // Le transtypage se fait sur le résultat de la méthode

La déclaration des variables TabObjet et TFS de type System.Array facilite l'écriture en évitant le transtypage suivant :

 
Sélectionnez

 TabObjet:=TArrayDynamiqueObjet(system.Array(TFS).Clone);

L'appel de Clone peut peut être remplacé par l'instruction Delphi native Copy :

 
Sélectionnez

 TabObjet:=Copy(TArrayDynFileStream(TFS));

La copie des valeurs d'un tableau contenant un type de base, des entiers par exemple, ne pose pas de problème en revanche la copie d'un tableau d'objets, contenant d'autres objets, ne recopiera que les références des objets imbriqués. Il s'agit donc d'une copie partielle du tableau.

4-1. Tableaux de constante

Projet : ..\Tableaux\ConstTab

Pour terminer, l'assignation d'une constante de type tableau statique dans une variable de tableau dynamique est possible, dans ce cas on peut modifier le tableau de constante :

 
Sélectionnez

program ConstTab;

{$APPTYPE CONSOLE}

uses
  SysUtils;

Const
 nbItem=9;
 CstTableauStatique : Array[0..nbItem] of Byte=(1, 2, 3, 4, 5, 9, 12, 15, 27, 33);

type
  TArrayDynamiqueSimple = Array of Byte;

var
  TableauDynamique : TArrayDynamiqueSimple;
  I :Byte;

begin
  {$R+}
  //{$R-}
  Writeln('CstTableauStatique[3]= ',CstTableauStatique[3]);
  //CstTableauStatique[3]:=5; // E2064 : La partie gauche n'est pas affectable
  
  Writeln('Assignation : TableauDynamique:=CstTableauStatique');
  TableauDynamique:=CstTableauStatique;
  
  Writeln('Assignation : TableauDynamique[3]:=65');
  TableauDynamique[3]:=65;
  
  Writeln('CstTableauStatique[3]= ',CstTableauStatique[3]); // = 65
  Writeln('TableauDynamique[3]= ',TableauDynamique[3]);
  
  readln;
end.

Vous pouvez consultez le chapitre 8 du tutoriel sur le langage Delphi pour .NET concernant le passage de paramètres.

Note :
Projet : ..\Tableaux\ConstTabW32

Sous Delphi Win32 cette opération est aussi possible en utilisant l'opérateur @.

5. L'initialisation de tableau

Projet : ..\Tableaux\LesBases23

Ce projet utilise différentes constructions pour initialiser un tableau.

 
Sélectionnez

Const Nbitem=9;

type
 TArrayStatic = Array[0..Nbitem] of Byte;
 TArrayDynamiqueSimple = Array of Byte;

var
 I : Byte;
 Tableau : TArrayStatic;

begin
 // Initialisation par boucle en connaissant la taille
 for I:=0 to Nbitem do
  begin
   Tableau[I]:=I;
  end;

  // Initialisation via un itérateur
 For I in Tableau do
  begin
   Tableau[I] := I;
  end;

  // Pour initialiser un tableau à zéro, similaire à Fillchar sous Win32
  // Pour un tableau statique Initialize est traduit en un appel à l'instruction IL newarr
 Initialize(Tableau);

  // Pour initialiser un tableau avec une valeur particulière, ici  0
 &Array.Clear(Tableau,0,Length(Tableau));

  //Initialisation par boucle sans connaitre la taille
 For I := 0 to Pred(Length(Tableau)) do 
  begin 
   Tableau[I] := I; 
  end;
end.

L'utilisation d'une boucle est à privilégier par rapport à l'appel d'un itérateur car elle génère moins de code.

La construction avec une liste de valeur constante est aussi possible :

 
Sélectionnez

type
 TArrayDynamiqueSimple = Array of Byte;

var
  Simple: TArrayDynamiqueSimple;

begin
 Simple:=TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
end.

Cette approche génère, dans le code compilé, 20 octets par valeur à insérer, la construction suivante étant préférable :

 
Sélectionnez

Const
 nbItem=9;

type
  TArrayDynamiqueSimple = Array of Byte;

var
  Simple: TArrayDynamiqueSimple;

begin
  Simple := New(TArrayDynamiqueSimple, 10);
  for I:=0 to nbItem do
   Simple[I]:=I;
end.

Il va sans dire que si vous utilisez des variables dans l'appel de la méthode Create cette astuce ne s'applique pas.

Revenons sur la syntaxe NEW, elle autorise deux formes :

  • l'une prend le type d'élément et la taille du tableau (syntaxe que nous avons déjà vue),
  • tandis que l'autre prend le type d'élément et une liste d'initialiseurs de tableaux.

Projet : ..\Tableaux\LesBases12

 
Sélectionnez

type
 TArrayDynamiqueSimple = Array of Byte;

var
  TableauDynamique: TArrayDynamiqueSimple;

begin
   // Alloue 256 octets. Array[0..255], initialisés à zéro
  // Première forme : le type d'élément et la taille du tableau
  TableauDynamique := New(TArrayDynamiqueSimple, 256);
  Write('Valeur de TableauDynamique[0] =',TableauDynamique[0]);
  Writeln(' Nombre d''élément=', System.Array(TableauDynamique).GetLength(0).ToString);

  // Alloue 1 octet. Array[0..0], initialisé à 255
  // Seconde forme : le type d'élément et une liste d'initialiseurs de tableaux.
  TableauDynamique := New(array[] of byte,(255));
  Write('Valeur de TableauDynamique[0] =',TableauDynamique[0]);
  Writeln(' Nombre d''élément=', System.Array(TableauDynamique).GetLength(0).ToString);
  Readln;
end.

Le résultat étant successivement le suivant :

 
Sélectionnez

Valeur de TableauDynamique[0] =0    Nombre d'élément=256
Valeur de TableauDynamique[0] =255  Nombre d'élément=1

Il est possible d'utiliser des initialiseurs pour des objets plus complexes :

 
Sélectionnez

 C: array [] of TPoint;    // tableau de TPoint

begin
   // New prenant une liste d'initialiseurs de TPoint.
 C := New(array[] of TPoint, ((X:1;Y:2), (X:3;Y:4)) );

6. Tableau avec un indice négatif

Projet : ..\Tableaux\TableauNegatif

Il reste une autre possibilité de créer un tableau unidimensionnel qui passe par l'utilisation de la méthode System.Array.CreateInstance qui permet de créer un tableau avec 1 indice différent de zéro.
Extrait du SDK .NET 1.1 :

 
Sélectionnez

Contrairement à la plupart des classes, Array fournit la méthode CreateInstance plutôt que des constructeurs
publics, permettant ainsi l'accès à liaison tardive.
 
Sélectionnez

var
  // On peut utiliser System.Array si le type du tableau est inconnu à la compilation
  Simple: TArrayDynamiqueSimple; 

   // CreateInstance attend 2 tableaux d'entier  une dimension)
  LowerBounds,
  Lengths : TIntegerDynArray;

begin
   // Création des 2 tableaux nécessaires
  LowerBounds:= New(TIntegerDynArray,1);  // Contient les bornes inférieures
  Lengths:=New(TIntegerDynArray,1); // Contient la longueur d'une dimension
 
  // Initialisation des valeurs
  LowerBounds[0]:=1; // Bornes inférieures
  Lengths[0]:=10;    // Longueur

   //Création du tableau souhaité : Array [1..10] Of Byte
  Simple:=System.Array.CreateInstance(typeof(Byte),Lengths,LowerBounds) as TArrayDynamiqueSimple;
end.

On peut donc utiliser les initialiseurs et réduire la taille du code :

 
Sélectionnez

   //Création du tableau souhaité : Array [1..10] Of Byte
  Simple:=System.Array.CreateInstance(Typeof(Byte),
				 New(array[] of integer,(10)),
				 New(array[] of integer,(1))) as TArrayDynamiqueSimple;

Le code précédent est une démonstration du pourquoi faire simple quand on peut faire compliquer...

La méthode CreateInstance est une des méthodes utilisées avec les tableaux multidimensionnels alloués dynamiquement.

En revanche cette syntaxe peut être utile si on souhaite créer un tableau avec des indices négatif, ce que l'instruction New ne permet pas pour un tableau SZ:

 
Sélectionnez

 TableauDynamiqueSimple := New(TArrayDynamiqueSimple,-256);
 Writeln('TableauDynamiqueSimple négatif');
 ShowInfoTableau(TableauDynamiqueSimple);

Dans ce cas les informations affichées sont les suivantes :

 
Sélectionnez

Nombre total d'éléments dans toutes les dimensions = 0
Nombre de dimension  = 1
Nombre d'éléments(0) = 0
GetLowerBound(0)     = 0
GetUpperBound(0)     = -1

En utilisant CreateInstance :

 
Sélectionnez

   // Alloue 256 octets. Array[-256..-1]
  LowerBounds:= TArrayDynamiqueInteger.Create(-256); // Déclare 1 dimensions et indique le premier indice
  Lengths:=TArrayDynamiqueInteger.Create(256);       // Déclare la longueur du tableau [-256..-1]
  TableauDynamiqueObjet:=System.Array.CreateInstance(typeof(Byte),Lengths,LowerBounds);
  Writeln('TableauDynamiqueSimple négatif');
  ShowInfoTableau(TableauDynamiqueObjet);

Dans ce cas les informations affichées sont les suivantes :

 
Sélectionnez

Nombre total d'éléments dans toutes les dimensions = 256
Nombre de dimension  = 1
Nombre d'éléments(0) = 256
GetLowerBound(0)     = -256
GetUpperBound(0)     = -1

On s'aperçoit aussi que le type du tableau n'est plus :

 
Sélectionnez

 System.Byte[]

mais

 
Sélectionnez

 System.Byte[*]

Ce tableau n'est donc plus un Tableau SZ ni CLS-Compliant.

6-1. Problématiques d'itération

Sur ce type de tableau les syntaxes d'itération suivantes ne fonctionnent plus :

 
Sélectionnez

 // Low(Simple) renvoie zéro
 For I:=Low(Simple) to High(Simple) do writeln('Simple[',I,']',simple[I]);

  // L'itérateur pointe sur une zone erronée
 For I in Simple do writeln('Simple[',I,']',simple[I]);

  //Déclenche System.IndexOutOfRangeException
 For I:=-255 to -1 do
   writeln('Simple[',I,'], writeln(Simple[I]);

Pour résoudre le problème du calcul de l'indice on utilisera :

 
Sélectionnez

 For I:=System.Array(Simple).GetLowerBound(0) to System.Array(Simple).GetUpperBound(0) do

Note : Vous ne pouvez pas non plus transmettre un tableau multidimensionnel alloué dynamiquement à la fonction Low ou High. Si vous essayez de le faire, une erreur de compilation se produira.

Pour résoudre le problème d'accès à la valeur l'indicée on utilisera :

 
Sélectionnez

   // Pour renseigner une valeur
   // ici le contexte nécessite un double cast
  System.Array(Simple).SetValue(TObject(Byte(I)),I);

   // Pour accéder à une valeur
  writeln('Simple[',I,']',System.Array(Simple).GetValue(I));

7. Tableaux multidimensionnels alloués dynamiquement

Pour déclarer un tableau dynamique multidimensionnel, on utilise :

  • soit une construction de type array of array of type,
  • soit array[,] of type.

Pour créer un tableau irrégulier (jagged Array ou ragged Array) on utilisera la première méthode.

La construction de type array[0..X,0..Y] of type est dédié aux tableaux multidimensionnels statiques.

La création et l'assignation de tableau multidimensionnel diffère peu de ce que nous avons vu jusqu'ici, bien que l'opérateur NEW ait été conçu principalement pour ce type de tableau.

7-1. Manipulation de tableau de tableaux

Projet : ..\Tableaux\LesBases3

Les tableaux de tableaux peuvent être créés en utilisant l'instruction New :

 
Sélectionnez

type
  TArrayDynamiqueSimple = Array of Byte;

   // Array of Array of Byte;
  TArrayDynamiqueMultiple = Array of TArrayDynamiqueSimple;

   // Array of Array of Array of Byte;
  TArrayDynamique3D = Array of TArrayDynamiqueMultiple; 

   // Array of Array of Array of Array of Byte;  
  TArrayDynamique4D = Array of TArrayDynamique3D;  

var  
  TableauDynamiqueMultiple : TArrayDynamiqueMultiple;
  TableauDynamique3 : TArrayDynamique3D;
  TableauDynamique4 : TArrayDynamique4D;
  
begin  
   // Alloue  2*2 Octets. Array[0..1][0..1]
  TableauDynamiqueMultiple := New(TArrayDynamiqueMultiple, 2,2);

   // Alloue  3*2*8 Octets. Array[0..2][0..1][0..7]
  TableauDynamique3 := New(TArrayDynamique3D,3,2,8);

   // Alloue  7*2*8*10 Octets. Array[0..6][0..1][0..7][0..9]
  TableauDynamique4 := New(TArrayDynamique4D,7,2,8,10);

Ces tableaux sont initialisés à zéro.
On peut aussi les créer de cette maniére :

 
Sélectionnez

  TableauDynamiqueMultiple:=TArrayDynamiqueMultiple.Create(
          TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
          TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15));

   // Array [0..1][0..1][0..7]
   TableauDynamique3:=TArrayDynamique3D.Create(
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85))),
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85)))
                              );

  TableauDynamique4 := TArrayDynamique4D.Create(
                        TArrayDynamique3D.Create(
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85))),
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85)))),
                        TArrayDynamique3D.Create(
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85))),
                          (TArrayDynamiqueMultiple.Create(
                              TArrayDynamiqueSimple.Create(1, 2, 3, 4, 5, 9, 12, 15),
                              TArrayDynamiqueSimple.Create(71, 72, 73, 74, 75, 79, 88, 85)))),
                              );

Ce qui confirme qu'un array of array attend bien des types référence et non des types valeur.

Mais le code généré est très bavard et l'imbrication d'appel alourdi la lecture du code. Etant donné que les valeurs sont connues à la compilation autant utiliser des tableaux de constantes, dans ce cas la taille du code généré est divisée par 4 :

 
Sélectionnez

Const
  Nbitem=8;
   // Créé dans le constructeur de classe
  cstArray1:Array[0..Nbitem-1] of byte=(1, 2, 3, 4, 5, 9, 12, 15);
  cstArray2:Array[0..Nbitem-1] of byte=(71, 72, 73, 74, 75, 79, 88, 85);
  ...
begin  
  // Construction à privilégier pour initialiser un tableau
 TableauDynamiqueMultiple:=TArrayDynamiqueMultiple.Create(cstArray1,cstArray2);

 TableauDynamique3:=TArrayDynamique3D.Create(
                           TArrayDynamiqueMultiple.Create(cstArray1,cstArray2),
                           TArrayDynamiqueMultiple.Create(cstArray1,cstArray2));
 

La construction suivante est aussi possible mais nécessite un peu plus de code que la version précédente :

 
Sélectionnez

 TableauDynamiqueSimple:= New(array[] of Byte,(5, 3, 2, 4, 1, 9, 12, 15));
 TableauDynamiqueMultiple:=TArrayDynamiqueMultiple.Create(
          TableauDynamiqueSimple,
          TableauDynamiqueSimple);
  // ou mieux
 TableauDynamiqueMultiple:=TArrayDynamiqueMultiple.Create(
          New(array[] of Byte,cstArray1),
          New(array[] of Byte,cstArray1));

le code suivant est identique et compatible Delphi Win32 et Delphi .NET :

 
Sélectionnez

  SetLength(TableauDynamiqueMultiple, 2); // nombre de ligne
  SetLength(TableauDynamiqueMultiple[0], 8); // nombre de colonne de la ligne 1
  SetLength(TableauDynamiqueMultiple[1], 8); // nombre de colonne de la ligne 2
  
  TableauDynamiqueMultiple[0,0] := 5;
  TableauDynamiqueMultiple[0,1] := 3;
  ...
  TableauDynamiqueMultiple[0,7] := 15;
  TableauDynamiqueMultiple[1,0] := 5;
  TableauDynamiqueMultiple[1,1] := 3;
  ...
  TableauDynamiqueMultiple[1,7] := 15;
  for i:=0 to 1 do
    for j:=0 to 2 do
        writeln(TableauDynamiqueMultiple[i,j]);
  Readln;
end.

7-2. Manipulation de tableau multidimensionnel

Projet : ..\Tableaux\LesBases31

Les tableaux multidimensionnels peuvent être créés en utilisant les 2 autres syntaxes de New :

 
Sélectionnez

   // Le code suivant est issue de la documentation de Delphi 2005.
var
  a : array [,,] of integer;  // tableau tridimensionnel
  b : array [,] of integer;   // tableau à 2 dimensions
  c : array [,] of TPoint;    // tableau à 2 dimensions de TPoint

begin
    // New prenant le type d'élément et la taille de chaque dimension.
    // Spécifie le nombre de dimension et pas la taille réelle.
   a := New(array[3,5,7] of integer);  

    // New prenant le type d'élément et une liste d'initialiseurs.
   b := New(array[,] of integer, ((1,2,3), (4,5,6))); 

    // Spécifie le nombre de dimension et la taille réelle.                                                     
    // New prenant une liste d'initialiseurs de TPoint.
   c := New(array[,] of TPoint, (((X:1;Y:2), (X:3;Y:4)), ((X:5;Y:6), (X:7;Y:8))));
end.

Voici un autre exemple.
Créons un tableau puis initialisons ces cellules une à une :

 
Sélectionnez

type
  TArrayDynamiqueInteger = Array of Integer;
  TArray2D = Array[,] of byte;

var
  Tableau2D : TArray2D;

  // CreateInstance attend 2 tableaux d'entier  une dimension)
  LowerBounds,
  Lengths :  TArrayDynamiqueInteger;

  i,j : Integer;
  N : Byte;

begin
{$R-} // Nécessaire avec les constructions suivantes, sinon erreur d'accés sur le tableau

   // Les indices débute à zéro
  Tableau2D := New(array[,] of Byte, ((1,2,3), (4,5,6)));
  ShowInfoTableau(Tableau2D);
  For N in Tableau2D do
    Write(N);
  Writeln;

  I:=2;
  J:=3;
  // Initialisé à zéro
  Tableau2D := New(array[I,J] of Byte);
  ShowInfoTableau(Tableau2D);
  For N in Tableau2D do
    Write(N);
  Writeln;

  readln;
end.

7-3. La méthode CreateInstance

La méthode CreateInstance peut aussi être utilisée pour créer un tableau multidimensionnel débutant à indice différent de zéro ou non :

 
Sélectionnez

//Projet : ..\Tableaux\LesBases31

   // Array[1..4,2..8]
  LowerBounds:= TArrayDynamiqueInteger.Create(1,2); // ici 2 dimensions. Peut être (200,1,45,5,10) = 5 dimensions
  Lengths:=TArrayDynamiqueInteger.Create(4,8);      // la taille de chaque dimensions
  Tableau2D:=System.Array.CreateInstance(typeof(Byte),Lengths,LowerBounds) as TArray2D;

  Writeln('Tableau à 2 dimensions');
  ShowInfoTableau(Tableau2D);
  For N in Tableau2D do
   Write(N);
  Writeln;

L'obligation de déclarer la directive $R- pour les tableaux multidimensionnels semble être un bug.

8. Remarque sur l'instruction initialize

Pour un tableau statique du type

 
Sélectionnez

type
 TArrayStatic = Array[0..9] of Byte;

Initialize(array) est traduit en un appel à l'instruction IL newarr (création d'un nouveau tableau).

Pour un tableau dynamique du type

 
Sélectionnez

type
  TArrayDynamique= Array of Byte;

Initialize(array) est traduit en un appel aux instructions IL ldnull puis stloc.

Note : L'instruction Finalyze(array) n'est pas implémentée sous Delphi .NET.

Attention à ne pas confondre l'instruction Delphi Initialize avec l'appel de méthode System.Array.Initialize.

9. Quelques fonctions utiles

Unité : ..\Tableaux\UInfoArray

La fonction suivante nous permet de déterminer si un tableau est un tableau SZ ou non. Pour ce faire on teste si le nombre de dimension est égal à 1 et si la borne inférieure du tableau est 0 :

 
Sélectionnez

Function IsSZArray(Origine:System.Array):Boolean;
 // Renvoie True si le tableau est un tableau simple, une dimension  basé zéro
 //  Un tableau de tableau est considéré comme un tableau simple
 //  Un tableau de System.String est considéré comme un tableau simple
Begin
  Result:=(Origine.Rank=1) And (Origine.GetLowerBound(0)=0);
end;

Ici on teste si le tableau Origine à une dimension égale à 1 et une borne inférieure égale à zéro.

Certains choix dans la fonction précédente nous incite à déterminer si un tableau est un tableau de tableau :

 
Sélectionnez

Function IsArrayOfArray(Origine:System.Array):Boolean;
 // renvoie true si le tableau est un tableau de tableau
 // Array of String renvoie faux, bien que String soit un Array of Char
 //
 // Provoque une exception NullReferenceException si une dimension du tableau Origine n'est pas initialisée
var Tableau: System.Array;
begin
 Result:=False;
  // Si Origine est un tableau d'objet on ne le considére pas, au vue de sa définition, comme
  // un tableau de tableau.
 If Not (Origine.GetType.GetElementType=TypeOf(System.Object)) then
   // Détermine le Type de l'objet auquel le tableau en cours fait référence.
  if Origine.GetType.GetElementType.IsArray then
   Result:=True;
end;

L'appel de la méthode Origine.GetType renvoie le type de la classe d'origine (System.Array) puis l'appel de GetElementType renvoie le type de l'objet auquel le tableau fait référence. Ici il y a 2 appels imbriqués.

TypeOf(System.Object) renvoie le type d'après un nom de classe. Pour plus d'informations consulter le tutoriel introduction au système de réflexion sous .NET (C#).

Nativement il n'est pas possible de connaître le niveau d'imbrication d'un tableau de tableau, le code suivant permet d'obtenir cette information :

 
Sélectionnez

Function GetNestedLevel(Origine:System.Array):Byte;
 // Renvoie la 'profondeur d'un tableau de tableau
 // Array of byte       = 1
 // Array of Array byte = 2 ...
 //
 // Si une dimension du tableau Origine n'est pas initialisée cela provoquera
 // une exception NullReferenceException dans la méthode IsArrayOfArray
type
 TableauD2 =Array[0..0,0..0] of TObject;
 TableauD3 =Array[0..0,0..0,0..0] of TObject;

begin
 Result:=1;
 While IsArrayOfArray(Origine) do
 begin
  case Origine.Rank of
      //Array[0..0]
   1: Origine:=System.Array(Origine[Origine.GetLowerBound(0)]);
       //Array[0..0,0..0]
   2: Origine:=System.Array(TableauD2(Origine)[Origine.GetLowerBound(0),
                                               Origine.GetLowerBound(1)]);
       //Array[0..0,0..0,0..0]
   3: Origine:=System.Array(TableauD3(Origine)[Origine.GetLowerBound(0),
                                               Origine.GetLowerBound(1),
                                               Origine.GetLowerBound(2)]);
   // Pour les autres dimensions -> erreur
  end;
  Inc(Result);
 end;
end;

Pour déterminer si un objet est du type tableau on peut utiliser la fonction IsArray du système de réflexion. Extrait du SDK .NET 1.1 :

 
Sélectionnez

Il se peut que Type.IsArray et Type.GetElementType ne retournent pas les résultats attendus avec Array,
car si un tableau est casté en type Array, le résultat est un objet, et non pas un tableau.
Cela signifie que typeof(System.Array).IsArray retourne false et que typeof(System.Array).GetElementType
retourne une référence nil.

On utilisera donc une variable intermédiaire pour récupérer l'information :

 
Sélectionnez

Function IsAnArray(Origine:TObject):Boolean;
var TabType :System.Type;
begin
 TabType:=Origine.GetType;
 Result:=TabType.IsArray; // Origine.GetType.IsArray est possible;
end;

L'affichage du type d'un tableau simple renvoie typeArray [], celui d'un tableau multidimensionnel renvoie typeArray [,] ([,,], [,,,], ...) et celui d'un tableau de tableau typeArray [][] ([][][], ...).

Les procédures suivantes nous permettront d'afficher la plupart des informations pertinentes concernant une variable de type tableau :

 
Sélectionnez

Procedure ShowBound(Origine:System.Array);
var S : String;
    I : Byte;
begin
 with Origine do
  for i:=0 to Rank-1 do
   begin
    Writeln('Type d''élément du tableau : '+ GetType.GetElementType.ToString);
    Writeln('Taille fixe : '+IsFixedSize.ToString);
    Writeln('Nombre de dimension = '+Rank.ToString);
    S:=I.ToString;
    Writeln('Nombre d''éléments('+S+') = '+GetLength(I).ToString);
    Writeln('GetLowerBound('+S+')      = '+GetLowerBound(I).ToString);
    Writeln('GetUpperBound('+S+')      = '+GetUpperBound(I).ToString);
   end;
end;

Procedure ShowInfoTableau(Origine:System.Array);
begin
 with Origine do
  begin
    Writeln('Variable tableau '+IsAnArray(Origine).ToString);
    Writeln('Niveau d''imbrication '+GetNestedLevel(Origine).ToString);
    Writeln('Tableau de tableau : '+ IsArrayOfArray(Origine).ToString);
    Writeln('Type du tableau : '+GetType.ToString);
    Writeln('CLS-Compliant '+ Boolean(GetLowerBound(0)=0).ToString);
    Writeln('Tableau simple : '+Boolean(IsSZArray(Origine)).ToString);
    Writeln('Taille : '+ IntToStr(SizeOf(Origine) ));
    Writeln('nombre total d''éléments dans toutes les dimensions = '+Length.ToString);
    ShowBound(Origine)
  end;
end;

Sachez qu'un tableau ne peut être en lecture seul (IsReadOnly) sauf si vous passez par l'usage d'une propriété, le fichier source array.cs précédemment cité nous le confirme :

 
Sélectionnez

 public abstract class Array : ICloneable, IList
 {
   ...
   public virtual bool IsReadOnly {
    get { return false; }
   }
 ...  

Sachez aussi que la méthode IsFixedSize renvoie toujours True.

 
Sélectionnez

 public abstract class Array : ICloneable, IList
 {
   ...
  public virtual bool IsFixedSize {
   get { return true; }
  }
 ... 

Projet : ..\Tableaux\LesBases13

Pour terminer la fonction SizeOf sur un type ou une variable tableau renvoi la valeur 4, la taille d'un pointeur sur Intel 32 bits. Pour obtenir la taille d'un tableau utilisez Length.

10. Classes apparentées

10-1. BitArray

Projet : ..\Tableaux\BitArray1

Cette classe gère un tableau compact de valeurs de bit représentées par des Boolean, où true indique que le bit est activé (1) et false indique que le bit est désactivé (0).

10-2. Les champs de bits

Cette possibilité n'est pas construite autour d'un tableau, je la signale juste à titre indicatif.
Voir le détail dans ce tutoriel.

10-3. ArrayList

Implémente l'interface IList à l'aide d'un tableau dont la taille augmente dynamiquement selon les besoins.
La classe Borland.Vcl.Classes.TList est mappée sur System.Collections.ArrayList

11. Conclusion

Toutes les syntaxes liées à la gestion des tableaux Win32, sauf Finalyze(array), peuvent être utilisées sous Delphi .NET.
On utilisera les méthodes ou les syntaxes dédiées à .NET pour

  • optimiser le code,
  • manipuler des types de tableaux que ne permet Delphi Win32,
  • bien évidemment, le développement de logiciels qui ne seront pas portés sous Win32.

J'aborderais plus tard la copie complète d'un tableau d'objet contenant d'autres objets (deep copy).