Gestion des exceptions X++

Note

Les groupes d’intérêt communautaire sont maintenant 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 Communauté Viva Engage Finances et Opérations et choisissez la communauté que vous souhaitez rejoindre.

Cet article décrit la gestion des exceptions dans X++. Gérer les erreurs en utilisant le lancer, essaie... capturez,enfin et réessayez les instructions pour générer et gérer les exceptions.

Une exception est un saut régulé qui s’éloigne de la séquence d’exécution du programme. Les try...catch blocs et le type d’exception lancé déterminent l’instruction où l’exécution du programme reprend. Une exception est représentée par une valeur de l’énumération d’exception , ou une instance de . NET System.Exception ou une classe dérivée de celle-ci. Une exception que vous lancez souvent est la valeur enum Exception :: error. Une pratique courante consiste à écrire des informations diagnostiques dans l’Infolog avant de lancer l’exception.

La méthode Global ::error est souvent la meilleure façon d’écrire des informations diagnostiques dans l’Infolog. Par exemple, votre méthode pourrait recevoir une valeur de paramètre d’entrée qui n’est pas valide. Dans ce cas, la méthode peut générer une exception pour transférer immédiatement le contrôle vers un bloc de code catch contenant une logique pour gérer cette situation d’erreur. Vous n’avez pas nécessairement besoin de connaître l’emplacement du bloc de capture qui reçoit le contrôle lorsque l’exception est lancée.

Lancer des déclarations

Utilisez le mot-clé throw pour lancer une valeur enum d’Exception . Par exemple, l’instruction suivante génère une exception d’erreur.

throw Exception::error;

Au lieu de lancer une valeur enum, utilisez la sortie de la méthode Global ::error comme opérande pour le lancement.

throw Global::error('The parameter value is invalid.');

La méthode Global ::error peut automatiquement convertir une étiquette en texte correspondant. Cette fonctionnalité vous aide à écrire du code qui peut être localisé plus facilement.

throw Global::error("@SYS98765");

Vous pouvez appeler les méthodes statiques sur la classe Global sans le préfixe Global :: . Par exemple, vous pouvez appeler la méthode Global ::error comme ceci.

error('My message.');

Utilisez le mot-clé throw pour lancer les exceptions .NET ainsi que les valeurs du type d’exception énuméré.

throw new System.InvalidOperationException("This function is not allowed");

Utilisez le mot-clé lancer seul dans un bloc de capture. Dans ce cas, throw se comporte comme l’instruction rethrow dans C#. L’exception originale, le message d’exception et son contexte, comme la pile d’appels, sont relancés et accessibles à toute instruction catch dans le code d’appel.

try
{
    throw Exception::error;
}
catch
{
    // locally handle exception
    // then rethrow for caller
    throw;
}

les instructions try, catch, finally et retry

Lorsqu’une exception survient, le système la traite d’abord via la liste de capture du bloc d’essai le plus interne. Si le système trouve un bloc de capture qui gère le type d’exception qui a eu lieu, le contrôle du programme passe à ce bloc de capture . Si la liste d’attrapés n’a pas de bloc spécifiant l’exception, le système transmet l’exception à la liste d’attrapés du prochain bloc d’essai le plus interne. Le système traite les instructions catch dans la même séquence qu’elles apparaissent dans le code.

Il est courant que la première instruction catch gère la valeur enum Exception ::Error . Une stratégie consiste à faire en sorte que l’instruction de la dernière capture ne soit pas spécifiée, le type d’exception. Dans ce cas, l’instruction de capture finale traite toutes les exceptions qui ne sont pas traitées par une déclaration de capture antérieure. Cette stratégie convient à la tentative la plus extérieure... Attrape des blocs.

Vous pouvez inclure une clause finalement optionnelle dans try... Attrape des déclarations. La sémantique d’une proposition finally est la même qu’en C#. Le système exécute les instructions de la clause finally lorsque le contrôle quitte le bloc try , soit normalement, soit par une exception.

L’instruction retry ne peut apparaître que dans un bloc catch . L’instruction retry fait sauter control à la première ligne de code dans le bloc try associé. Utilisez l’instruction retry lorsque la cause de l’exception peut être fixée par le code dans le bloc catch . L’instruction retry donne au code dans le bloc try une autre chance de réussir. L’instruction retry efface tous les messages qui ont été écrits dans l’Infolog depuis que le contrôle du programme est entré dans le bloc try .

Note

Vous devez vous assurer que vos instructions retry ne provoquent pas une boucle infinie. Comme bonne pratique, le bloc d’essai devrait inclure une variable que vous pouvez tester pour savoir si vous êtes dans une boucle.

try
{
    // Code here.
}
catch (Exception::Numeric)
{
    info("Caught a Numeric exception.");
}
catch
{
    info("Caught an exception.");
}
finally
{
    // Executed no matter how the try block exits.
}

Le gestionnaire d’exception système

Si aucune instruction catch ne gère l’exception, c’est le gestionnaire d’exception du système qui la traite. Le gestionnaire d’exception système n’écrit pas dans l’Infolog. Par conséquent, les exceptions non traitées peuvent être difficiles à diagnostiquer. Suivez toutes ces directives pour assurer une gestion efficace des exceptions :

  • Ayez un bloc try qui contient toutes vos instructions dans la trame la plus externe de la pile d’appels.
  • Ayez un bloc de prise non qualifié à la fin de votre liste de prises les plus externes.
  • Évitez de lancer directement une valeur enum d’Exception .
  • Lancez la valeur enum que l’une des méthodes suivantes sur la classe Global retourne : Global ::error, Global ::warning, ou Global ::info. (Vous pouvez omettre le préfixe implicite Global :: ).
  • Quand vous détectez une exception que l’Infolog ne montre pas, appelez la fonction Global ::info pour l’afficher.

Exception ::CLRError, Exception ::UpdateConflictNotRecovered, et les exceptions du noyau système sont des exemples d’exceptions que l’Infolog ne montre pas automatiquement.

Exceptions et interopérabilité CLR

Vous pouvez appeler des classes et méthodes du Microsoft .NET Framework qui résident dans des assemblages gérés par le common language runtime (CLR). Lorsque votre code génère une instance .NET Framework System.Exception , vous pouvez l’attraper en déclarant une variable de type System.Exception pour capturer toute exception .NET, ou vous pouvez attraper un type d’exception .NET spécifique en utilisant l’une de ses classes dérivées, comme montré dans l’exemple suivant.

System.ArgumentException ex;
try
{
    throw new System.ArgumentException("Invalid argument specified");
}
catch(ex) // Will catch the System.ArgumentException, given the type of ex.
{
    error(ex.Message);
}

Vous pouvez détecter les exceptions .NET en consultant Exception ::CLRError. Votre code peut obtenir une référence à l’instance System.Exception en appelant la méthode CLRInterop ::getLastException .

try
{
    // call to .NET code which throws exception
}
catch(Exception::CLRError)
{
    System.Exception ex = CLRInterop::getLastException();
    error(ex.Message);
}

S’assurer que les exceptions sont indiquées

L’Infolog n’affiche pas d’exceptions du type Exception ::CLREror , car ces exceptions ne sont pas générées par un appel à une méthode comme Global ::error. Dans votre bloc de capture , votre code peut appeler Global ::error pour signaler l’exception spécifique.

Méthodes de classes globales

Cette section décrit certaines méthodes de classes globales plus en détail. Ces méthodes de classe incluent Global ::error, Global ::info et Global ::exceptionTextFallThrough.

Méthode global ::error

Le code suivant montre comment la méthode d’erreur est déclarée.

static Exception error
    (SysInfoLogStr txt,
    URL helpURL = '',
    SysInfoAction _sysInfoAction = null)

Le type de retour est la valeur enum Exception :: Error. La méthode d’erreur ne génère pas d’exception. Il fournit simplement une valeur enum que vous pouvez utiliser dans une instruction throw . L’énoncé de lancer lance l’exception. Voici des descriptions des paramètres pour la méthode de l’erreur . Seul le premier paramètre est requis.

  • SysInfoLogStr txt est un str du texte du message. Il peut aussi être une référence d’étiquette, comme strFmt(« @SYS12345 », strThingName).
  • L’URL helpUrl fait référence à l’emplacement d’un article d’aide dans Application Explorer, comme « KernDoc :\\\\Functions\\substr ». La valeur du paramètre est ignorée si _sysInfoAction est fournie.
  • SysInfoAction est une instance d’une classe qui étend la classe SysInfoAction. Les overrides de méthode que nous recommandons pour la classe enfant sont la méthode description , la méthode run , la méthode pack et la méthode unpack .

Méthode global ::info

Utilisez la méthode Global ::info pour afficher le texte dans l’Infolog. Dans les programmes, écrivez-le comme info(« Mon message. »);. Bien que la méthode info renvoie une valeur enum Exception ::Info , vous voulez rarement lancer Exception ::Info, car rien d’inattendu ne s’est produit.

Méthode Global ::exceptionTextFallThrough

Parfois, tu veux ne rien faire à l’intérieur de ton bloc de réception . Cependant, le compilateur X++ génère un avertissement si vous avez un bloc de capture vide. Pour éviter cet avertissement, appelez la méthode Global ::exceptionTextFallThrough dans le bloc catch . La méthode ne fait rien, mais elle satisfait le compilateur et énonce explicitement l’intention.

Exceptions dans les transactions

Si une exception est déclenchée à l’intérieur d’une transaction, celle-ci est automatiquement annulée (c’est-à-dire qu’une opération ttsAbort se produit). Ce comportement s’applique autant aux exceptions lancées manuellement qu’aux exceptions lancées par le système. Lorsqu’une exception est jetée à l’intérieur d’un bloc de transaction ttsBegin-ttsCommit , aucune instruction catch à l’intérieur de ce bloc de transaction ne peut traiter l’exception (sauf s’il s’agit d’un UpdateConflict ou d’une DuplicateKeyException). Au lieu de cela, les instructions de capture les plus internes qui se trouvent en dehors du bloc de transaction sont les premières instructions de capture qui sont testées.

Pour détecter UpdateConflict ou DuplicateKeyException dans une transaction, spécifiez explicitement l’exception dans l’instruction catch comme ceci, catch (Exception::DuplicateKeyException). Une instruction catch{} ne peut pas détecter UpdateConflict ou DuplicateKeyException à l’intérieur d’une transaction.

La clause final est exécutée même dans la portée de la transaction.

Exceptions et using énoncés

La portée des exceptions n’affecte pas la sémantique des using énoncés. La using déclaration :

using (var athing = new SomethingDisposable())
{
    // Do work.
}

Est sémantiquement identique à :

var athing = new SomethingDisposable();
try
{
    // Do work.
}
finally
{
    if (athing != null)
        athing.Dispose();
}

Exemples de gestion des exceptions

Affichage des exceptions dans l’Infolog

L’exemple de code suivant montre des exceptions dans l’Infolog.

// This example shows that a direct throw of Exception::Error does not
// display a message in the Infolog. This is why we recommend the
// Global::error method.
static void TryCatchThrowError1Job(Args _args)
{
/***
    The 'throw' does not directly add a message to the Infolog.
    The exception is caught.
***/
    try
    {
        info("In the 'try' block. (j1)");
        throw Exception::Error;
    }
    catch (Exception::Error)
    {
        info("Caught 'Exception::Error'.");
    }

/**********  Actual Infolog output
Message (03:43:45 pm)
In the 'try' block. (j1)
Caught 'Exception::Error'.
**********/
}

Utiliser la méthode de l’erreur pour écrire les informations d’exception dans l’Infolog

L’exemple de code suivant utilise la méthode d’erreur pour écrire les informations d’exception dans l’Infolog.

// This example shows that the use of the Global::error method
// is a reliable way to display exceptions in the Infolog.
static void TryCatchGlobalError2Job(Args _args)
{
    /***
    The 'Global::error()' does directly add a message to the Infolog.
    The exception is caught.
    ***/
    try
    {
        info("In the 'try' block. (j2)");
        throw Global::error("Written to the Infolog.");
    }
    catch (Exception::Error)
    {
        info("Caught 'Exception::Error'.");
    }

/***  Infolog output
Message (03:51:44 pm)
In the 'try' block. (j2)
Written to the Infolog.
Caught 'Exception::Error'.
***/
}

Manipulation d’un CLRError

L’exemple de code suivant traite une exception CLREror .

// This example shows that a CLRError exception is not displayed
// in the Infolog unless you catch the exception and manually
// call the info method. The use of the CLRInterop::getLastException
// method is also demonstrated.
static void TryCatchCauseCLRError3Job(Args _args)
{
    /***
    The 'netString.Substring(-2)' causes a CLRError,
    but it does not directly add a message to the Infolog.
    The exception is caught.
    ***/
    System.String netString = "Net string.";
    System.Exception netExcepn;
    try
    {
        info("In the 'try' block. (j3)");
        netString.Substring(-2); // Causes CLR Exception.
    }
    catch (Exception::Error)
    {
        info("Caught 'Exception::Error'.");
    }
    catch (Exception::CLRError)
    {
        info("Caught 'Exception::CLRError'.");
        netExcepn = CLRInterop::getLastException();
        info(netExcepn.ToString());
    }

/**********  Actual Infolog output (truncated for display)
Message (03:55:10 pm)
In the 'try' block. (j3)
Caught 'Exception::CLRError'.
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. --->
    System.ArgumentOutOfRangeException: StartIndex cannot be less than zero.
Parameter name: startIndex
    at System.String.InternalSubStringWithChecks(Int32 startIndex, Int32 length, Boolean fAlwaysCopy)
    at System.String.Substring(Int32 startIndex)
    at ClrBridgeImpl.InvokeClrInstanceMethod(ClrBridgeImpl* , ObjectWrapper* objectWrapper, Char* pszMethodName,
    Int32 argsLength, ObjectWrapper** arguments, Boolean* argsAreByRef, Boolean* isException)
**********/
}

Utilisation d’une instruction retry

L’exemple de code suivant utilise une instruction retry .

// This example shows how to use the retry statement. The print
// statements are included because retry causes earlier Infolog
// messages to be erased.
static void TryCatchRetry4Job(Args _args)
{
    /***
    Demonstration of 'retry'. The Infolog output is partially erased
    by 'retry', but the Print window is fully displayed.
    ***/
    Exception excepnEnum;
    int nCounter = 0;
    try
    {
        info("        .");
        print("        .");
        info("In the 'try' block, [" + int2str(nCounter) + "]. (j4)");
        print("In the 'try' block, [" + int2str(nCounter) + "]. (j4)");
        nCounter++;
        if (nCounter >= 3) // Prevent infinite loop.
        {
            info("---- Will now throw a warning, which is not caught.");
            print("---- Will now throw a warning, which is not caught.");
            throw Global::warning("This warning will not be caught. [" + int2str(nCounter) + "]");
        }
        else
        {
            info("Did not throw a warning this loop. [" + int2str(nCounter) + "]");
            print("Did not throw a warning this loop. [" + int2str(nCounter) + "]");
        }
        excepnEnum = Global::error("This error message is written to the Infolog.");
        throw excepnEnum;
    }
    catch (Exception::Error)
    {
        info("Caught 'Exception::Error'.");
        print("Caught 'Exception::Error'.");
        retry;
    }
    info("End of job.");
    print("End of job.");

/**********  Actual Infolog output
Message (04:33:56 pm)
            .
In the 'try' block, [2]. (j4)
---- Will now throw a warning, which is not caught.
This warning will not be caught. [3]
**********/
}

Lancer une exception dans une transaction

L’exemple de code suivant génère une exception dans un bloc de transaction.

// This examples uses three levels of try nesting to illustrate
// where an exception is caught when the exception is thrown inside
// a ttsBegin ... ttsCommit transaction block.
static void TryCatchTransaction5Job(Args _args)
{
    /***
    Shows an exception that is thrown inside a ttsBegin - ttsCommit
    transaction block cannot be caught inside that block.
    ***/
    try
    {
        try
        {
            ttsbegin;
            try
            {
                throw error("Throwing exception inside transaction.");
            }
            catch (Exception::Error)
            {
                info("Catch_1: Unexpected, caught in 'catch' inside the transaction block.");
            }
            ttscommit;
        }
        catch (Exception::Error)
        {
            info("Catch_2: Expected, caught in the innermost 'catch' that is outside of the transaction block.");
        }
    }
    catch (Exception::Error)
    {
        info("Catch_3: Unexpected, caught in 'catch' far outside the transaction block.");
    }
    info("End of job.");

/**********  Actual Infolog output
Message (04:12:34 pm)
Throwing exception inside transaction.
Catch_2: Expected, caught in the innermost 'catch' that is outside of the transaction block.
End of job.
**********/
}

Utiliser Global ::error avec un paramètre SysInfoAction

Quand votre code génère une exception, il peut écrire des messages dans l’Infolog. Vous pouvez rendre ces messages Infolog plus utiles en utilisant la classe SysInfoAction .

Dans l’exemple suivant, vous passez un paramètre SysInfoAction à la méthode Global ::error . La méthode d’erreur écrit le message dans l’Infolog. Lorsque l’utilisateur double clique sur le message Infolog, la méthode SysInfoAction.run s’exécute.

Dans la méthode exécution , vous pouvez écrire du code qui aide à diagnostiquer ou corriger le problème qui a causé l’exception. L’objet que vous passez à la méthode Global ::error est construit à partir d’une classe que vous écrivez et qui étend SysInfoAction.

L’exemple de code suivant est présenté en deux parties.

  • La première partie montre une tâche qui appelle la méthode Global ::error puis lance la valeur retournée. Vous passez une instance de la classe SysInfoAction_PrintWindow_Demo à la méthode d’erreur .
  • La deuxième partie montre la classe SysInfoAction_PrintWindow_Demo .

Partie 1 : Appel de Global ::error

static void Job_SysInfoAction(Args _args)
{
    try
    {
        throw Global::error
            ("Click me to make the Print window display."
            ,""
            ,new SysInfoAction_PrintWindow_Demo()
            );
    }
    catch
    {
        warning("Issuing a warning from the catch block.");
    }
}

Partie 2 : La classe SysInfoAction_PrintWindow_Demo

public class SysInfoAction_PrintWindow_Demo extends SysInfoAction
{
    str m_sGreeting; // In classDeclaration.
    public str description()
    {
        return "Starts the Print Window for demonstration.";
    }
    public void run()
    {
        print("This appears in the Print window.");
        print(m_sGreeting);

        /*********** Actual Infolog output
        Message (03:19:28 pm)
        Click me to make the Print window display.
        Issuing a warning from the catch block.
            ***************/
    }
    public container pack()
    {
        return ["Packed greeting."]; // Literal container.
    }
    public boolean unpack(container packedClass, Object object = null)
    {
        [m_sGreeting] = packedClass;
        return true;
    }
}

Liste des exceptions

Le tableau suivant montre les littéraux d’exception qui sont les valeurs de l’énumération d’exception .

Littéral d’exception Description
Pause L’utilisateur appuyait sur Break ou Ctrl+C.
CLRError Une erreur est survenue pendant l’utilisation de la fonctionnalité CLR.
CodeAccessSecurity Une erreur est survenue pendant que la méthode CodeAccessPermission.demand était utilisée.
DDEerror Une erreur est survenue pendant l’utilisation de la classe système DDE .
Impasse Un blocage de base de données s’est produit, car plusieurs transactions attendent les unes les autres.
DuplicateKeyException Une erreur s’est produite lors d’une transaction utilisant le contrôle optimiste de la concurrence. La transaction peut être retentée (utilisez une instruction retry dans le bloc catch ).
DuplicateKeyExceptionNotRecovered Une erreur s’est produite lors d’une transaction utilisant le contrôle optimiste de la concurrence. Le code ne sera pas réessayé. Cette exception ne peut pas être incluse dans une transaction.
Erreur Une erreur fatale s’est produite. La transaction a été arrêtée.
Info Cette exception contient littéralement un message pour l’utilisateur. Ne faites pas d’exception d’information .
Interne Une erreur interne est survenue dans le système de développement.
Numérique Une erreur est survenue pendant que les fonctions str2int, str2int64 ou str2num étaient utilisées.
Séquence
Mise à jourConflit Une erreur s’est produite lors d’une transaction utilisant le contrôle optimiste de la concurrence. La transaction peut être retentée (utilisez une instruction retry dans le bloc catch ).
Mise à jourConflitNotRecovered Une erreur s’est produite lors d’une transaction utilisant le contrôle optimiste de la concurrence. Le code ne sera pas réessayé. Cette exception ne peut pas être incluse dans une transaction.
Alerte Un événement exceptionnel s’est produit. Bien que l’utilisateur puisse devoir agir, l’événement n’est pas fatal. Ne faites pas d’exception d’avertissement .
Exception erreur de connexion SQL X++ Une erreur est survenue lors de l’exécution de la requête. La transaction sera annulée. Cette exception ne peut pas être incluse dans une transaction.
Expiration du délai L’exécution des requêtes SQL a expiré. L’exception ne peut pas être incluse dans une transaction. L’exception peut être réessayée en utilisant une instruction retry dans le bloc catch.