Types de données composites X++

Remarque

Les groupes d’intérêt communautaire sont désormais passés de Yammer à Microsoft Viva Engage. Pour rejoindre une communauté Viva Engage et participer aux dernières discussions, remplissez le formulaire Demande d’accès à la Finance et Opérations Viva Engage Communauté et choisissez la communauté que vous souhaitez rejoindre.

Cet article décrit les types de données composites en X++. Les types de données composites dans X++ sont les tableaux, les conteneurs, les classes en tant que types de données, les délégués en tant que types de données et les tables en tant que types de données.

Tableau

Un tableau est une variable qui contient une liste d’éléments ayant le même type de données. Les éléments d’un tableau sont accessibles à l’aide d’index entiers. Vous utilisez une instruction distincte pour initialiser chaque élément d’un tableau. Lorsque vous utilisez un type de données conteneur ou un objet tableau pour créer une collection, vous pouvez initialiser plusieurs éléments à l’aide d’une seule instruction. Par défaut, tous les éléments d’un tableau ont la valeur par défaut du type de données dans le tableau. Il existe trois types de matrices : les matrices dynamiques, les matrices de longueur fixe et en partie les matrices de disques.

  • Tableaux dynamiques : ces tableaux sont déclarés à l’aide d’une option de tableau vide. En d’autres termes, ils n’ont que des crochets ([]).
  • Tableaux de longueur fixe : ces tableaux peuvent contenir le nombre d’éléments spécifié dans la déclaration. Les tableaux de longueur fixe sont déclarés comme des tableaux dynamiques, mais une option de longueur est incluse entre parenthèses.
  • En partie sur les grappes de disques : ces matrices sont déclarées comme des matrices dynamiques ou des matrices de longueur fixe qui ont une option supplémentaire qui déclare le nombre d’éléments à conserver en mémoire. Les autres éléments sont stockés sur le disque et sont automatiquement chargés lorsqu’ils sont référencés.

X++ ne prend en charge que les tableaux unidimensionnels. Toutefois, vous pouvez imiter le comportement de plusieurs index de tableau. (Pour plus d’informations, consultez la section Index de tableaux multiples ). Les variables des objets et des tables peuvent être déclarées sous forme de tableaux. Par exemple, cette fonctionnalité est utilisée dans les lignes d’adresse de l’application standard. Une classe de collection de tableaux vous permet de stocker des objets dans un tableau.

Les index de tableau commencent à 1. Le premier élément du tableau est référencé comme [1], le deuxième élément est référencé comme [2], et ainsi de suite. La syntaxe suivante est utilisée pour accéder à un élément de tableau : ArrayItemReference = ArrayVariable [ Index ]. Dans cette syntaxe, ArrayVariable est l’identificateur du tableau et Index est le numéro de l’élément du tableau. L’index peut être une expression entière. L’élément zéro [0] est utilisé pour effacer le tableau. Si une valeur est affectée à l’index 0 dans un tableau, tous les éléments du tableau sont réinitialisés à leur valeur par défaut.

L’affectation d’un tableau entier à un autre est effectuée par référence.

Exemples de tableaux

public void ArrayMethod()
{
    int myArray[10]; // Fixed-length array with 10 integers.
    myArray[4] = 1; // Accessing the 4th element in the array.
    myArray[0] = 0; // Resets all elements in intArray.

    // Dynamic array of integers.
    int intArray[];

    // Dynamic array of variables of type Datatype.
    //Datatype arrayVariable[];

    // Fixed-length arrays.
    boolean boolArray[100]; // Fixed-length array of booleans with 100 items.

    // Two examples of Partly On Disk Arrays.
    // Dynamic integer array with only 100 elements in memory.
    int arrayVariable [ ,100];
    // Fixed-length string array with 1000 elements, and only 200 in memory.
    str arrayVariable2 [1000,200];

    // A dynamic array of integers.
    int i[];
    // A fixed-length real array with 100 elements.
    real r[100];
    // A dynamic array of dates with only 10 elements in memory.
    date d[,10];
    // A fixed length array of NoYes variables with 100 elements and 10 in memory.
    NoYes ny[100,10];
}

Index de tableaux multiples

Certains langages, tels que C++ et C#, vous permettent de déclarer des tableaux qui ont plus d’un index. En d’autres termes, vous pouvez définir des « tableaux de tableaux ». Dans X++, vous ne pouvez pas créer directement plusieurs index de tableau, car seuls les tableaux unidimensionnels sont pris en charge. Toutefois, vous pouvez implémenter plusieurs index à l’aide de la méthode décrite dans cette section. Par exemple, vous souhaitez déclarer un tableau qui a deux dimensions, pour contenir un montant gagné par pays/région par dimension. Il y a 10 pays/régions et trois dimensions. En C++ et C#, vous déclarez le tableau suivant.

// This is C# or C++ code, not X++ code.
long earning[10, 3];

Toutefois, X++ ne prend pas en charge cette déclaration. Au lieu de cela, vous pouvez définir un tableau unidimensionnel où le nombre d’éléments est le produit des éléments de chaque dimension. En voici un exemple.

public void MultipleArrayMethod()
{
    // Step 1: define a one-dimensional array with the number
    // of elements that is the product of the elements in each dimension.
    real earnings[10*3];

    // Step 2: to refer to a specific element, such as earnings[i,j], write the following:
    // declare i and j (maybe) and assign the value to something
    int i = 1;
    int j = 2;
    real element = earnings[(i-1)*3 + j];
}

// This can be written into a macro like this:
#localmacro.earningIndex
(%1-1)*3+%2
#endmacro

public void CallTheMacro()
{
    // Next, call the specific element within the macro like this:
    int i = 1;
    int j = 2;
    real element = earnings[#earningIndex(i,j)];

    // The previous scheme can be extended to any number of dimensions.
    // The element a[i1, i2, ..., ik] can be accessed by computing the
    // offset into an array containing (d1*d2*...*dk) elements.
    //(i1 - 1)*d2*d3*..*dk +
    //(i2 - 1)*d3*d4*...*dk + .... +
    //(ik-1 -1)*dk +
    //(ik-1)
}

Conteneur

Un objet conteneur est une liste dynamique d’éléments qui contient des types de données primitifs ou des types de données composites. Un conteneur est utile lorsque vous devez passer différents types de valeurs entre les couches client et serveur. Toutefois, si vous prévoyez d’ajouter plusieurs éléments à une liste dans une boucle, un conteneur n’est pas un bon choix. Les conteneurs sont les plus adaptés aux processus qui n’impliquent pas de modification excessive de la taille ou du contenu du conteneur. Lorsqu’un conteneur subit des ajouts excessifs de données, les performances globales du système peuvent diminuer, car les données du conteneur doivent être copiées à plusieurs reprises et un nouvel espace doit être alloué à plusieurs reprises.

Un conteneur n’est pas une classe. Un conteneur contient une séquence ordonnée de valeurs primitives ou d’autres conteneurs. En raison de la flexibilité de n’importe quel type, un conteneur offre un bon moyen de stocker des valeurs de différents types ensemble. Un conteneur peut être stocké dans la base de données. Un conteneur est l’un des types de colonnes que vous pouvez sélectionner lorsque vous utilisez l’Explorateur d’applications pour ajouter une colonne à une table. Un conteneur ressemble légèrement à un tableau ou à des collections, telles que les classes List ou Stack . Toutefois, vous ne pouvez jamais modifier la taille ou le contenu d’un conteneur après sa création.

Les instructions X++ qui semblent modifier un conteneur créent en interne un nouveau conteneur et copient les valeurs selon les besoins. Même l’affectation d’un conteneur à une autre variable de conteneur crée une nouvelle copie du conteneur. Toutes ces opérations peuvent affecter les performances. Dans les fonctions qui permettent d’accéder à un conteneur (comme conPeek), le conteneur est basé sur 1 et non sur 0. L’indexation est basée sur 1 pour les tableaux. La valeur par défaut d’un conteneur est vide. Le conteneur ne contient aucune valeur. Certaines instructions qui utilisent des conteneurs peuvent sembler modifier un conteneur, mais à l’intérieur du système, les conteneurs ne sont jamais modifiés. Au lieu de cela, les données du conteneur d’origine sont combinées avec les données de la commande pour créer un nouveau conteneur. Vous pouvez créer un conteneur à l’aide de l’une des fonctions suivantes : conDel, conIns ou conPoke.

De plus, la classe Global dispose de méthodes statiques pour la gestion des conteneurs. Ces méthodes incluent con2ArraySource, con2Buf, con2List, con2Str, containerFromXmlNode, conView et str2Con. Il existe plusieurs fonctions intrinsèques pour la gestion d’un conteneur, telles que conIns et conPeek. La fonction X++ conPeek renvoie un type anytype , par conséquent, il est plus facile de lire les valeurs d’un conteneur lorsque vous ne connaissez pas le type de chaque valeur. Un anytype peut être attribué à n’importe quel type de valeur X++, à condition que la valeur puisse être convertie. Votre code est plus facile à lire lorsqu’il évite les conversions explicites de types de données. Par conséquent, attribuez des valeurs d’un conteneur au même type de données que celui utilisé pour placer la valeur dans le conteneur. Vous ne devez pas affecter un conteneur à anytype , car le système risque de ne pas être en mesure de déterminer les conversions correctes. Dans ce cas, un comportement inattendu ou des erreurs peuvent se produire.

Comparaison du conteneur avec d’autres options

Le type de conteneur ressemble à d’autres constructions, telles que les tableaux et les classes de collection, telles que List et Stack. La différence entre un conteneuret List est qu’une instance de la classe List est modifiable. Un objet List alloue d’abord plus d’espace que ses données n’en consomment. Ensuite, au fur et à mesure que des données sont ajoutées, l’espace est rempli. Ce comportement est plus efficace que l’allocation de plus d’espace chaque fois qu’un élément est ajouté. La mise à jour d’une liste s’effectue plus rapidement que des opérations similaires sur un conteneur.

Lorsque vous construisez un objet List , vous déterminez le type de données que l’objet List peut stocker. Cette restriction est moins souple pour une liste que pour un conteneur. Toutefois, vous pouvez stocker des objets dans une liste, tandis qu’un conteneur ne peut stocker que des types valeur. La différence entre un conteneur et un tableau est qu’un tableau ne peut contenir que des éléments de son type déclaré. Vous pouvez allouer de l’espace mémoire à un tableau et remplir cet espace avec des valeurs ultérieurement. Par exemple, vous pouvez renseigner des valeurs dans une boucle. Ce comportement est efficace et fonctionne bien. Lorsque vous souhaitez créer un conteneur en ajoutant de nouvelles données, vous pouvez utiliser l’opérateur += ou la fonction conIn . L’opérateur += est l’alternative la plus rapide. N’utilisez la fonction conIns que si vous souhaitez ajouter de nouvelles données avant le dernier index des données d’origine.

Vous ne pouvez pas stocker de références d’objet dans des conteneurs. Lorsque le compilateur détecte une tentative de stockage d’une référence d’objet dans un conteneur, il émet un message d’erreur. Si le type de l’élément ajouté au conteneur est anytype, le compilateur ne peut pas déterminer si la valeur est un type de référence. Dans ce cas, le compilateur autorise la tentative. Bien que le compilateur ne diagnostique pas le code comme étant erroné, une erreur sera générée au moment de l’exécution.

Exemples de conteneurs

public void ContainerExample()
{
    // First, declare the variables you are using.
    container myContainer;
    container myContainer4;
    container myContainer5;
    // Three ways to declare a container.
    myContainer = [1];
    myContainer += [2];
    myContainer4 = myContainer5;

    // Declare a container.
    container cr3;

    // Assign a literal container to a container variable.
    cr3 = [22, "blue"];

    // Declare and assign a container.
    container cr2 = [1, "blue", true];

    // Mimic container modification (implicitly creates a copy).
    cr3 += [16, strMyColorString];
    cr3 = conIns(cr3, 1, 3.14);
    cr3 = conPoke(cr3, 2, "violet");

    // Assignment of a container (implicitly creates a copy).
    cr2 = cr3;

    // Read a value from the container.
    str  myStr = conPeek(cr2, 1);

    // One statement that does multiple assignments from a container.
    str myStr;
    int myInt;
    container cr4 = ["Hello", 22, 20\07\1988];
    [myStr, myInt] = cr4; // "Hello", 22

    // Example of applying the = operator to a container. The example
    // initializes myContainer2 and myContainer33.
    myContainer2 = [2, "apple"];

    // Next, you make a copy of myContainer33 and assign the copy to myContainer2.
    myContainer33 = [33, "grape"];
    myContainer2 = myContainer33;  // The container that myContainer2 had been holding is no longer available and cannot be recovered.
    // An example of building a new container by
    // assigning a new value to myContainer33 through the += operator.
    myContainer33 += [34, "banana"];
}

// Container example. In this example, variable2 and variable33 hold different containers.
static void JobC(Args _args)
{
    container variable2, variable33;
    variable2 += [98];
    variable33 = variable2;
    variable2 += [97];
}

// List class example. In this example, variable2 and variable33 refer to the same List object.
static void JobL(Args _args)
{
    List variable2,variable33;
    variable2 = new List(Types::Integer);
    variable2.addEnd(98);
    variable33 = variable2;
    variable2.addEnd(97);
}

// The automatic type conversion by anytype also applies to the special syntax for making multiple
// assignments from a container in one statement. This is shown in the following code example,
// which assigns a str to an int, and an int to a str.
static void JobContainerMultiAssignmentUsesAnytype(Args _args)
{
    container con2;
    int int4;
    str str7;
    con2 = ["11", 222];
    [int4, str7] = con2;
    info(strfmt("int4==11==(%1), str7==222==(%2)", int4, str7));
}

/***  Output:
Message (10:36:22 am)
int4==11==(11), str7==222==(222)
***/

static void UseQuery()
{
    // An example of how the compiler diagnoses attempts to store object in containers
    container c = [new Query()];   // This statement will cause the error message shown below.
    /*** Instance of type 'Query' cannot be added to a container. ***/

    // An example of a code that won't cause an error message, but will
    // cause an error message to be thrown at runtime.
    anytype a = new Query();
    container d = [a];
}

Classes en tant que types de données

Une classe est une définition de type qui décrit à la fois les variables et les méthodes pour les instances de la classe. (Les instances d’une classe sont également appelées objets.) Une classe n’est qu’une définition pour les objets, et tous les objets sont null lorsqu’ils sont déclarés. Dans l’Explorateur d’applications, chaque classe d’application sous le nœud Classes est un type de données. Vous pouvez déclarer des variables de ces types dans votre code. Vous pouvez construire des instances d’une classe et les affecter à des variables.

Les classes peuvent être imbriquées dans le code source. Les classes imbriquées ne sont disponibles qu’à l’intérieur des formulaires (par exemple, une classe qui étend FormRun) et sont utilisées pour représenter des contrôles, des sources de données ou des champs de données. Une décoration d’attribut, telle que la décoration d’attribut sur une classe ou une méthode, peut omettre le suffixe du nom de l’attribut si le suffixe est Attribute. Par conséquent, X++ autorise [MyFavorite] au lieu d’exiger [MyFavoriteAttribute]. De plus, les attributs sont désormais appliqués aux gestionnaires de délégués et de méthodes, afin de mapper les gestionnaires à ces cibles.

Dans AX 2012 et les versions antérieures, vous pouviez désigner une méthode à exécuter sur le client ou le serveur. Toutefois, dans les applications financières et opérationnelles, tout le code X++ compilé est exécuté en tant que langage CIL (Common Intermediate Language) .NET sur le serveur. Il n’y a plus de code qui est évalué sur le site client ou dans le navigateur. Par conséquent, les mots-clés client et serveur sont désormais ignorés. Bien que ces mots-clés ne provoquent pas d’erreur de compilation s’ils sont utilisés, ils ne doivent pas être utilisés dans un nouveau code.

Variables membres privées et protégées

Auparavant, toutes les variables membres définies dans une classe étaient protégées. Vous pouvez désormais rendre explicite la visibilité des variables membres en ajoutant les mots-clés private, protected et public . L’interprétation de ces modificateurs est évidente et est alignée avec la sémantique des méthodes :

  • private – La variable membre ne peut être utilisée que dans la classe où elle est définie.
  • protected – La variable membre peut être utilisée dans la classe où elle est définie et dans toutes les sous-classes de cette classe.
  • public – La variable membre peut être utilisée n’importe où. Il est visible en dehors des limites de la hiérarchie de classe où il est défini.

Par défaut, les variables membres qui ne sont pas ornées d’un modificateur explicite sont toujours protégées. Toutefois, il est recommandé de spécifier explicitement la visibilité. Comme nous l’avons décrit précédemment, lorsqu’une variable membre est définie comme publique, elle est accessible en dehors de la classe où elle est définie. Dans ce cas, vous devez spécifier un qualificateur qui désigne l’objet qui héberge la variable. Pour spécifier le qualificateur, utilisez la notation par points, comme vous le faites pour les appels de méthode.

Dans l’exemple suivant, l’accès à field1 se fait à l’aide du qualificateur explicite this . Dans ce cas, ce n’est peut-être pas une bonne idée de rendre publique une variable membre, car cette approche expose le fonctionnement interne de la classe à ses consommateurs, et crée donc une forte dépendance entre l’implémentation de la classe et ses consommateurs. Vous devriez toujours essayer de ne dépendre que d’un contrat, pas d’une mise en œuvre.

public class AnotherClass3
{
    int field1;
    str field2;
    void new()
    {
        this.field1 = 1;   // Explicit object designated.
        field2 = "Banana";  // 'this' assumed, as usual.
    }
}

Constructeurs statiques et champs statiques

Les champs statiques sont des champs qui sont déclarés à l’aide du mot-clé stat . D’un point de vue conceptuel, les champs statiques s’appliquent à la classe, et non aux instances de la classe. Il est garanti que les constructeurs statiques s’exécutent avant que des appels statiques ou des appels d’instance ne soient effectués à la classe. L’exécution du constructeur statique est relative à la session de l’utilisateur. Vous n’appelez jamais explicitement le constructeur statique. Au lieu de cela, le compilateur générera du code pour s’assurer que le constructeur est appelé exactement une seule fois, avant que toute autre méthode de la classe ne soit appelée. Un constructeur statique est utilisé pour initialiser des données statiques ou effectuer une action qui ne doit être effectuée qu’une seule fois. Vous ne pouvez pas fournir de paramètres pour le constructeur statique, et il doit être marqué avec le mot-clé stat .

// An example of how a singleton (call instance in the example below)
// can be created using the static constructor.
public class Singleton
{
    private static Singleton instance;
    private void new()
    {
    }
    static void TypeNew()    // This is the static constructor.
    {
        instance = new Singleton();
    }

    public static Singleton Instance()
    {
        return Singleton::instance;
    }
}

// The singleton ensures that only one instance of the class
// will be called, which is consumed by the following.
{
    // Your code here.
    Singleton i = Singleton::Instance();
}

Éléments de classe dans l’Explorateur d’applications

Sous la plupart des nœuds de classe dans l’Explorateur d’applications, il existe deux nœuds spéciaux : un nœud classDeclaration et un nouveau nœud. Une classDeclaration contient toujours le mot-clé de classe X++. Des mots-clés supplémentaires, tels que extends, peuvent être inclus pour modifier la classe. Ce nœud peut également contenir des déclarations de variables membres.

Dans l’exemple suivant, les variables m_priority et m_rectangle sont membres de la classe.

// An example of a classDeclaration.
public class YourDerivedClass extends YourBaseClass
{
    int m_priority;
    Rectangle m_rectangle;
    void new(int _length, int _width)
    {
        this.m_rectangle = new Rectangle(_length, _width);
    }
}

Un nouvel opérateur contient une logique qui est exécutée lorsqu’il est utilisé pour créer une instance de la classe. La logique de la nouvelle méthode peut construire un objet et l’affecter à une variable déclarée dans classDeclaration. Chaque classe ne peut avoir qu’une seule nouvelle méthode. Cependant, dans la nouvelle méthode, vous devez souvent appeler la nouvelle méthode de la classe de base. Pour appeler la nouvelle méthode de la classe de base, appelez super().

L’exemple suivant montre la nouvelle méthode pour la classe YourDerivedClass dans l’exemple classDeclaration précédent. Dans cette nouvelle méthode, le code construit une instance de la classe Rectangle . L’instance est affectée à la variable m_rectangle . Le mot-clé this utilisé dans l’exemple est facultatif, mais si vous l’incluez, IntelliSense peut être plus utile.

// An example of the new method from the previous classDeclaration example.
void new(int _length, int _width)
{
    this.m_rectangle = new Rectangle(_length, _width);
}

Collecte des ordures ménagères

Finalement, pendant l’exécution, la plupart des objets n’ont plus de variable qui pointe vers eux. Le système recherche ces objets et les efface de la mémoire. L’espace mémoire devient alors disponible pour d’autres utilisations. La classe Object a une méthode nommée finalize. Cependant, la méthode finalize n’est pas un destructeur. Le runtime n’appelle jamais la méthode finalize , même lorsqu’un objet est collecté en tant que garbage.

Classes système

Dans l’Explorateur d’applications, sousClasses de > système, il existe une liste des classes de noyau ou des classes système. Les classes système ne sont pas écrites en X++ et vous ne pouvez pas voir leur code source. Vous ne pouvez pas ajouter de classes système. Les classes système ont généralement une nouvelle méthode, mais elles n’ont pas de nœud classDeclaration . Chaque classe d’application étend implicitement la classe système Object . Certaines classes système sont étendues par une classe d’application portant un nom similaire. Par exemple, xClassFactory est étendu par ClassFactory. Dans ce cas, vous ne devez pas utiliser la classe system. Pour plus d’informations, consultez « Remplacer les classes d’application par les classes système » dans Classes et méthodes.

Méthodes d’extension

La fonctionnalité de méthode d’extension vous permet d’ajouter des méthodes d’extension à une classe cible en les écrivant dans une classe d’extension distincte. Les règles suivantes s’appliquent :

  • La classe d’extension doit être statique.
  • Le nom de la classe d’extension doit se terminer par le suffixe de dix caractères _Extension, cependant, il n’y a aucune restriction sur la partie du nom qui précède le suffixe.
  • Chaque méthode d’extension de la classe d’extension doit être déclarée comme static publique.
  • Le premier paramètre de chaque méthode d’extension est le type d’extension qu’elle étend. Cependant, lorsque la méthode d’extension est appelée, l’appelant ne doit rien passer pour le premier paramètre. Au lieu de cela, le système transmet automatiquement l’objet requis pour le premier paramètre.
  • La cible d’une méthode d’extension doit être un type d’objet d’application de classe, de table, de vue ou de carte.

Une classe d’extension peut contenir des méthodes statiques privées ou protégées. Ces méthodes sont généralement utilisées pour les détails de l’implémentation et ne sont pas exposées en tant qu’extensions. La technique de la méthode d’extension n’affecte pas le code source de la classe qu’elle étend, par conséquent, l’ajout à la classe ne nécessite pas de surcouche.

Les mises à niveau de la classe cible ne sont jamais affectées par les méthodes d’extension existantes. Si une mise à niveau de la classe cible ajoute une méthode portant le même nom que votre méthode d’extension, celle-ci n’est plus accessible via les objets de la classe cible. La technique de la méthode d’extension utilise la même syntaxe délimitée par des points que celle que vous utilisez souvent pour appeler des méthodes d’instance régulières. Les méthodes d’extension peuvent accéder à tous les artefacts publics de la classe cible, mais elles ne peuvent pas accéder à tout ce qui est protégé ou privé. Par conséquent, les méthodes d’extension peuvent être considérées comme un type de sucre syntaxique. Quel que soit le type cible, une classe d’extension est utilisée pour ajouter des méthodes d’extension au type. Par exemple, une table d’extension n’est pas utilisée pour ajouter des méthodes à une table, et il n’existe pas de table d’extension.

// An example of an extension class holding a few extension methods.
public static class AtlInventLocation_Extension
{
    public static InventLocation refillEnabled(
        InventLocation _warehouse,
        boolean _isRefillEnabled = true)
    {
        _warehouse.ReqRefill = _isRefillEnabled;
        return _warehouse;
    }

    public static InventLocation save(InventLocation _warehouse)
    {
        _warehouse.write();
        return _warehouse;
    }
}

Délègues en tant que types de données

Un délégué collecte les méthodes qui s’y abonnent. Le délégué spécifie la signature de paramètre que toutes ses méthodes d’abonné doivent partager. Lorsque le délégué est appelé, il appelle chacun de ses abonnés. Un délégué ne renvoie jamais de valeur et ne peut pas avoir de valeur par défaut. Au début, chaque délégué n’a pas de méthodes abonnées. Il n’y a pas de limite au nombre de paramètres qu’un délégué peut déclarer, ni au type de ces paramètres. Le corps du délégataire est toujours vide, car le seul but du délégataire est de définir le contrat auquel les souscripteurs doivent se conformer. Il n’est pas nécessaire de définir un délégué dans une classe. Les délégués peuvent également être définis dans une table, un formulaire ou une requête.

Exemples de délégués

abstract class VarDatClass
{

    void new(utcdatetime _dateTime, str _changeDescription)
    {
        // An example of subscribing a delegate.
        this.notifyChanged += eventhandler(this.InfologChanges);
        this.notifyChanged += eventhandler(this.SaveInDatabase);
        
        notifyChange(_dateTime, _changeDescription);
    }

    void notifyChange(utcdatetime _dateTime, str _changeDescription)
    {
        // An example of calling a delegate.
        this.notifyChanged(_dateTime, _changeDescription);
    }
    
    // delegate method examples
    // An example of declaring a delegate.
    delegate void notifyChanged(utcdatetime _dateTime, str _changeDescription)
    {
    }

    // method that is to be subscribed.
    public static void InfologChanges(utcDateTime _dateTime, str _changeDescription)
    {
        info("A notification has occurred calling static handler:" +
            DateTimeUtil::toStr(_dateTime) +
            " Message:" +
            _changeDescription);
    }
    
    // method that is to be subscribed.
    public static void SaveInDatabase(utcDateTime _dateTime, str _changeDescription)
    {
       // save changes in database.
    }
    
    
}

Tables en tant que types de données

Toutes les tables peuvent être traitées comme des définitions de classe. Une variable de table peut être considérée comme une instance (objet) de la définition de table (classe). Pour chaque champ d’une variable de table, la valeur par défaut est vide. Vous pouvez adresser des champs et créer des méthodes sur des tables. Les méthodes peuvent être appelées sur des instances de la table. Pour manipuler (c’est-à-dire lire, mettre à jour, insérer et supprimer) des enregistrements dans des tables, vous devez déclarer au moins une variable de table qui peut contenir l’enregistrement actif. Il est recommandé d’utiliser le nom de la table comme nom de la variable, mais d’utiliser une lettre minuscule initiale. Voici quelques différences importantes entre les tables et les objets :

  • Vous ne pouvez pas allouer d’espace pour les variables de table. L’attribution se fait implicitement.
  • Les champs des variables de table sont publics. Vous pouvez les référencer n’importe où.
  • Les champs des variables de table peuvent être référencés à l’aide d’expressions.

Il n’y a pas de conversion automatique, mais les variables de table déclarées comme communes peuvent contenir les données de n’importe quelle table.

Portée des variables de table

À bien des égards, les variables de table peuvent être considérées comme des objets, cependant, contrairement aux objets, elles ne sont pas explicitement allouées. Seule une déclaration de variable est requise. Toutes les tables sont compatibles avec la table Common , tout comme tous les objets sont compatibles avec la classe Object . Les variables de table sont déclarées comme des tampons communs et peuvent être utilisées pour stocker les données de n’importe quelle table. Vous ne pouvez pas accéder aux tables qui n’ont pas de variables de table. Les principes de déclaration des variables de table et des objets sont les mêmes, sauf en ce qui concerne l’allocation de l’espace.

Exemples de tableaux

La syntaxe offre diverses possibilités de référencement des champs dans les enregistrements. Par exemple, vous pouvez utiliser le TableName.( FieldId).

L’exemple suivant imprime le contenu des champs de l’enregistrement actuel dans la table Customer.

// Declares and allocates space for one CustTable record.
public void myMethod()
{
    CustomerTable custTable;
}

// An example of referencing table variables.
public void printAccountNo()
{
    CustomerTable custTable;
    print custTable.AccountNo;  // Prints the field reference.
}

L’exemple suivant utilise les méthodes fieldCnt et fieldCnt2Id . La méthode fieldCnt compte le nombre de champs d’une table, tandis que fieldCnt2Id renvoie l’ID d’un numéro de champ. Par exemple, vous pouvez utiliser la méthode fieldCnt2Id pour apprendre que le champ numéro 6 d’une table a l’ID 54. Cette conversion est nécessaire, car il n’y a aucune garantie que les ID des champs d’une table soient consécutifs.

// An example of the various possibilities for referencing fields in records.
public void printCust()
{
    int i, n, k;
    CustomerTable custTable;
    DictTable dictTable;
    dictTable = new DictTable(custTable.TableId);
    n = dictTable.fieldCnt();
    print "Number of fields in table: ", n;
    for(i=1; i<=n; i++)
    {
        k = dictTable.fieldCnt2Id(i);
        print "The ", dictTable.fieldName(k),
        " field with Id=",k, " contains '",
        custTable.(k), "'";
    }
}