Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 variables en X++.
- Une variable est un identificateur qui pointe vers un emplacement mémoire où les informations d’un type de données spécifique sont stockées. La taille, la précision, la valeur par défaut, les fonctions de conversion implicites et explicites et la plage dépendent du type de données de la variable.
- La portée d’une variable définit la zone du code où un élément peut être accessible.
- Les variables d’instance sont déclarées dans les déclarations de classe, et vous pouvez y accéder depuis n’importe quelle méthode de la classe ou depuis des méthodes qui étendent la classe.
- Les variables locales ne peuvent être accessibles que dans le bloc où vous les avez définies.
- Lorsque vous déclarez une variable, vous allouez de la mémoire et initialisez la variable à la valeur par défaut.
- Vous pouvez attribuer des valeurs à la fois aux champs statiques et aux champs d’instance dans le cadre de l’instruction de déclaration.
- Vous pouvez déclarer des variables n’importe où dans un bloc de code d’une méthode. Vous n’êtes pas obligé de les déclarer au début d’une méthode.
- Les constantes sont des variables dont la valeur ne peut pas être modifiée lorsque vous déclarez la variable. Ils utilisent le mot-clé const ou readonly .
- Les constantes ne diffèrent des champs en lecture seule que d’une seule manière. Vous ne pouvez attribuer une valeur aux champs en lecture seule qu’une seule fois, et cette valeur ne change jamais. Vous pouvez attribuer sa valeur au champ soit en ligne, à l’endroit où le champ est déclaré, soit dans le constructeur.
Lorsque vous déclarez des variables de types gérés qui ne sont pas créés dans X++, vous avez deux options. Vous pouvez qualifier entièrement les noms de type dans la déclaration en incluant l’espace de noms complet, ou vous pouvez ajouter une instruction using à votre fichier, puis omettre l’espace de noms du nom de type.
Exemples de variables
// An example of two valid variable names.
str variableName;
CustInfo custNumber;
// An example of simultaneously declaring and initializing a variable.
real pi = 3.14159265359; // Assigns value of pi to 12 significant digits.
// An example of initializing an object by using the new method on the class.
Access accessObject = new Access(); // Simple call to the new method in the Access class.
// An example of multiple declarations using integers.
int i,j; // Declares 2 integers, i and j.
// An example of multiple declarations using an array.
int a[100,5], b=1; // Declares array with 100 integers with 5 in memory and b as an integer with value 1.
// An example of how variable scopes work.
class ScopeExample
{
// The variable a is declared within the class.
int a;
// Because the method below is declared within the class,
// it can access all the variables defined within the class.
void aNewMethod()
{
// The variable b is declared within the method.
// It can only be accessed by this method.
int b;
}
}
// An example of an assignment of field members inline.
public class FieldAssignmentExample
{
int field1 = 1;
str field2 = "Banana";
void new()
{
// ...
}
}
class ConstantExample
{
// An example of a constant being declared at the class level.
public const str MyContent = 'SomeValue';
public int ResultSoFar()
{
return 1;
}
}
// The constants can then be referenced by using the double-colon syntax.
str value = ConstantExample::MyContent;
// If you're in the scope of the class where the const is defined,
// you can omit the type name prefix (ConstantExample in this example).
// An example of the using clause where the alias can denote
// namespaces and classes.
using System;
using IONS=System.IO; // Namespace alias.
using Alist=System.Collections.ArrayList; // Class alias.
public class NamespaceExample
{
public static void Main(Args a)
{
Int32 I; // Alternative to System.Int32.
Alist al; // Using a class alias.
al = new Alist();
str s;
al.Add(1);
IONS.Path::ChangeExtension(@"c:\tmp\test.xml", ".txt");
}
}
Var
Vous pouvez déclarer une variable sans fournir explicitement le type de la variable si le compilateur peut déterminer le type à partir de l’expression d’initialisation. La variable reste fortement typée à un type non ambigu.
Vous pouvez utiliser la var uniquement sur les déclarations où vous fournissez des expressions d’initialisation. Le compilateur déduit le type à partir de l’expression d’initialisation. Dans certains cas, cette approche peut rendre le code plus facile à lire.
Utilisez var pour déclarer les variables locales dans ces situations :
- Lorsque le type de la variable est évident sur le côté droit de l’affectation
- Lorsque le type exact n’est pas important
- Pour les déclarations des compteurs de boucle for
- Pour les objets jetables à l’intérieur à l’aide de déclarations
N’utilisez pas var lorsque le type n’est pas clair dans l’expression d’initialisation.
Exemples de var
// When the type of a variable is clear from
// the context, use var in the declaration.
var var1 = "This is clearly a string.";
var var2 = 27; // This is an integer (not a real).
var i = System.Convert::ToInt32(3.4);
// Don't use var when the type of the variable is not clear
// from the context. Use an explicit type instead.
int var4 = myObject.ResultSoFar();
Déclarez n’importe où
Vous pouvez désormais fournir des déclarations où que vous puissiez en fournir un. Une déclaration est syntaxiquement une affirmation, donc c’est une déclaration de déclaration.
Vous pouvez fournir des déclarations immédiatement avant d’utiliser la variable, et vous n’êtes pas obligé de déclarer toutes les variables au même endroit. Par conséquent, vous avez un contrôle précis sur la portée de vos variables.
Vous pouvez donner des variables de champs d’application plus petits, en dehors desquels vous ne pouvez pas les référer. La durée de vie de la variable correspond à l’étendue dans laquelle elle est déclarée. Vous pouvez commencer des péripéties au niveau du bloc (à l’intérieur des instructions composées), dans les instructions for , et en utilisant des instructions.
Il y a plusieurs avantages à réduire la taille des lunettes :
- La lisibilité est améliorée.
- Vous réduisez le risque qu’une variable soit réutilisée de manière inappropriée lors de la maintenance à long terme du code.
- Il est plus facile de refactoriser le code. Vous pouvez copier du code sans avoir à craindre que les variables soient réutilisées dans des contextes où elles ne devraient pas l’être.
Dans l’exemple suivant, vous déclarez le compteur de boucle à l’intérieur de l’instruction for dans laquelle vous l’utilisez.
void MyMethod()
{
for (int i = 0; i < 10; i++)
{
info(strfmt("i is %1", i));
}
}
Le périmètre de la variable est l’instruction for elle-même, et elle inclut les parties expression condition et mise à jour de boucle. Vous ne pouvez pas utiliser la valeur en dehors de ce champ d’application.
Dans l’exemple suivant, lorsque le compilateur atteint l’instruction info , il renvoie le message d’erreur suivant : « 'i' n’est pas déclaré. »
void MyMethod()
{
for (int i = 0; i < 10; i++)
{
if (i == 7)
{
break;
}
}
// The next statement causes a compiler error.
info(strfmt("Found: %1", i));
}
Vous pouvez également limiter les variables à une instruction using , comme illustré dans l’exemple suivant.
static void AnotherMethod()
{
str textFromFile;
using (System.IO.StreamReader sr = new System.IO.StreamReader("c:\\test.txt"))
{
textFromFile = sr.ReadToEnd();
}
}
Lorsque vous utilisez un objet qui implémente IDisposable, déclarez et instanciez cet objet dans une instruction using . L’instruction using appelle correctement la méthode Dispose sur l’objet, même si une exception se produit pendant que vous appelez des méthodes sur l’objet. Vous pouvez obtenir le même résultat en plaçant l’objet à l’intérieur d’un bloc try , puis en appelant explicitement Dispose dans un bloc finally . En fait, le compilateur traduit l’instruction using de cette manière.
L’exemple suivant montre certaines des caractéristiques que cette section décrit.
// loop variable declared within the loop: It will
// not be misused outside the loop
for(int i = 1; i < 10; i++)
{
// Because this value is not used from outside the loop,
// its declaration belongs in this smaller scope.
str s = int2str(i);
info(s);
}
Pour éviter toute confusion, le compilateur renvoie un message d’erreur si vous essayez d’introduire une variable qui cache une autre variable dans un champ d’application d’enclos, ou même dans le même scope. Par exemple, le code suivant fait renvoyer par le compilateur le message de diagnostic suivant : « Une variable locale nommée 'i' ne peut pas être déclarée dans ce scope car elle donnerait un sens différent à 'i', qui est déjà utilisé dans un scope parent ou courant pour désigner autre chose. »
{
int i;
{
int i;
}
}
Constantes, variables en lecture seule et macros
Le langage prend pleinement en charge le concept de macros. Les constantes offrent les avantages suivants par rapport aux macros :
- Vous pouvez ajouter un commentaire de documentation à une constante, mais pas à la valeur d’une macro. Finalement, le service linguistique capte ce commentaire et fournit des informations utiles à l’utilisateur.
- IntelliSense reconnaît une constante.
- Une constante est référencée. Par conséquent, vous pouvez trouver toutes les références pour une constante spécifique, mais pas pour une macro.
- Une constante est soumise à des modificateurs d’accès. Vous pouvez utiliser les modificateurs privé, protégé et public . L’accessibilité des macros n’est pas rigoureusement définie.
- Les variables constantes ont une portée, alors que les macros n’en ont pas.
- Vous pouvez voir la valeur d’une constante ou d’une variable en lecture seule dans le débogueur.
Les macros que vous définissez dans les portées de classes (c’est-à-dire dans les déclarations de classes) sont effectivement disponibles dans toutes les méthodes de toutes les classes dérivées. Cette fonctionnalité était à l’origine un bug dans l’implémentation du macro du compilateur hérité. Cependant, de nombreux programmeurs d’applications en profitent aujourd’hui. Le compilateur X++ respecte toujours cette fonctionnalité, mais n’écrivez pas de nouveau code qui l’utilise. Cette fonctionnalité a également un effet significatif sur les performances du compilateur.
En option, vous pouvez utiliser une macro au niveau des déclarations de classe, mais avec une référence spécifique à la valeur dans cette macro. Cette approche n’a aucun impact sur les performances du compilateur. Vous pouvez permettre aux macros standards existantes d’avoir des références croisées (ainsi que toutes les capacités d’une variable constante) dans votre développement.
private const int RetryNum = #OCCRetryCount#RetryNum;
Vous pouvez déclarer des constantes au niveau de la classe, comme montré dans l’exemple suivant.
private const str MyConstant = 'SomeValue';
Vous pouvez référencer les constantes en utilisant la syntaxe des deux points ( ::).
str value = MyClass::MyConstant;
Si vous êtes dans le champ de la classe où vous définissez la constante (const), vous pouvez omettre le préfixe du nom du type (MyClass dans l’exemple précédent). Par conséquent, vous pouvez facilement mettre en œuvre le concept d’une bibliothèque de macros. La liste des symboles de macro devient une classe qui a des définitions publiques const .
Vous pouvez également définir des constantes en tant que variables uniquement. Le compilateur maintient l’invariant afin que la valeur ne puisse pas être modifiée.
{
const int Blue = 0x0000FF;
const int Green = 0x00FF00;
const int Red = 0xFF0000;
}
Vous ne pouvez attribuer une valeur aux champs en lecture seule qu’une seule fois, et cette valeur ne change jamais. Vous pouvez attribuer sa valeur au champ soit en ligne, à l’endroit où le champ est déclaré, soit dans le constructeur.
Valeurs nulles pour les types de données
De nombreux autres systèmes de gestion de bases de données (SGBD) prennent en compte le concept de valeurs nulles , mais X++ ne prend pas en place ce concept. Une variable dans X++ a toujours un type et une valeur. Cependant, pour chaque type de données, une valeur est considérée comme nulle (par exemple, lorsque la méthode validateField table s’exécute).
| Type | Valeur traitée comme null |
|---|---|
| Date | 1900-01-01 |
| Énumération | Un élément dont la valeur est définie sur 0 |
| Entier | 0 |
| Réel | 0.0 |
| Corde | Une corde vide |
| Temps | 00:00:00 |
| utcdatetime | Toute valeur dont la partie date est définie sur 1900-01-01, quelle que soit la valeur de la partie temps. Par exemple, la valeur 1900-01-01T22:33:44 est traitée comme null. Notez que l’instruction print X++ affiche toute valeur utcDateTime dont la partie date est fixée à 1900-01-01 comme vide. Seule la méthode Global ::info affiche la valeur 1900-01-01T00:00:00 comme vide. Cette valeur est la valeur de la méthode DateTimeUtil ::MinValue . |
Lorsque la méthode validateField vérifie si un utilisateur saisit une valeur dans un champ obligatoire, si 0 n’est pas accepté dans un champ de type entier , si la première entrée n’est pas acceptée dans un champ de type enum , et ainsi de suite. De plus, dans les instructions SQL X++, les valeurs répertoriées dans la table précédente donnent false dans une comparaison booléenne. Toutefois, dans les instructions X++ non-SQL, les opérateurs equal et relationnel fonctionnent avec ces valeurs, tout comme ils fonctionnent avec d’autres valeurs. Les variables de type conteneur et les classes et variables de type table peuvent être nulles au sens traditionnel du SGBD. Un type de table est null si tous ses champs ont leur valeur null .
Casting
Le casting fait référence aux affectations entre des variables dont les types déclarés se trouvent tous deux dans la même chaîne d’héritage. Un plâtre est soit un plâtre descendant, soit un plâtre ascendant. Considérez la hiérarchie de classe suivante.
La classe MotorVehicle n’est pas liée à la classe Animal, bien que les deux dérivent de Object. Un up-cast se produit lorsque vous assignez une expression d’un type dérivé à un type de base :
Animal a = new Horse();
Un down-cast se produit lorsque vous assignez une expression de type base à une variable dérivée.
Horse h = new Animal();
X++ prend en charge à la fois les up-casts et down-casts. Cependant, les descentes vers le bas sont dangereuses et il faut les éviter autant que possible. L’exemple descendu dans le code précédent échoue avec un InvalidCastException at runtime, car l’affectation n’a pas de sens. X++ prend en charge la liaison tardive sur quelques types, tels que objectformrunet . Ce support signifie que le compilateur ne diagnostique aucune erreur au moment de la compilation lorsqu’il voit une méthode appelée sur ces types, si cette méthode n’est pas explicitement déclarée sur ce type. Il est supposé que le développeur sait ce qu’il fait. Par exemple, le code suivant peut être trouvé dans un formulaire.
Object o = element.args().caller();
o.MyMethod(3.14, “Banana”);
Le compilateur ne peut pas vérifier les paramètres ou les valeurs de retour de la MyMethod méthode, car cette méthode n’est pas déclarée sur la classe d’objets. À l’exécution, l’appel est effectué en utilisant la réflexion, qui est beaucoup plus lente que les appels qui ne nécessitent pas de réflexion. Les appels vers des méthodes définies sur les types de liaison tardive sont vérifiés. Par exemple, l’appel à ToString dans l’exemple suivant :
o.ToString(45);
provoque une erreur de compilation :
'Object.toString' expects 0 argument(s), but 1 specified.
car la ToString méthode est définie sur la object classe. Il existe une différence par rapport à l’implémentation dans les versions précédentes de X++ (AX2012 et versions antérieures) liée au fait que les méthodes pouvaient être appelées sur des objets non liés, à condition que le nom de la méthode soit correct, même si les profils de paramètres n’étaient pas tout à fait corrects. Ce support n’est plus disponible.
Exemple - coulée
public class MyClass2
{
public static void Main(Args a)
{
Object obj = new Car();
Horse horse = obj; // exception now thrown
horse.run(); // Used to call car.run()!
}
}
Utilisez généreusement les is opérateurs et as dans votre code. Utilisez l’opérateur is pour vérifier si l’expression fournie est d’un type particulier (y compris les types dérivés). L’opérateur as effectue un casting dans le type donné et renvoie null si un cast n’est pas possible.