Gestió d'excepcions X++

Nota

Els grups d'interès comunitari ara s'han traslladat de Yammer a Microsoft Viva Engage. Per unir-te a una comunitat Viva Engage i participar en les últimes discussions, omple el formulari Sol·licita accés a la Comunitat Viva Engage de Finances i Operacions i tria la comunitat a la qual vols unir-te.

En aquest article es descriu la gestió d'excepcions en X++. Gestiona els errors utilitzant el llançament, prova... capturar, finalment i reintentar instruccions per generar i gestionar excepcions.

Una excepció és un salt regulat de la seqüència d'execució del programa. Els try...catch blocs i el tipus d'excepció que es llança determinen la instrucció on es reprèn l'execució del programa. Una excepció es representa amb un valor de l'enumeració Exception o una instància de . NET o una classe derivada d'ella System.Exception . Una excepció que sovint llences és el valor enum Exception:: error. Una pràctica habitual és escriure informació diagnòstica a l'Infolog abans de llençar l'excepció.

El mètode Global::error és sovint la millor manera d'escriure informació de diagnòstic a l'Infolog. Per exemple, el mètode pot rebre un valor de paràmetre d'entrada que no és vàlid. En aquest cas, el mètode pot llançar una excepció per transferir immediatament el control a un bloc de codi catch que conté lògica per gestionar aquesta situació d'error. No cal que coneguis necessàriament la ubicació del bloc de captura que rep el control quan es llança l'excepció.

Declaracions de llançament

Utilitza la paraula clau throw per llançar un valor enum d'Exception . Per exemple, la sentència següent llança una excepció d'error.

throw Exception::error;

En lloc de llençar un valor d'enum, utilitza la sortida del mètode Global::error com a operand per al llançament.

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

El mètode Global::error pot convertir automàticament una etiqueta en el text corresponent. Aquesta funcionalitat us ajuda a escriure codi que es pot localitzar més fàcilment.

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

Pots cridar els mètodes estàtics a la classe Global sense el prefix Global:: . Per exemple, pots anomenar el mètode Global::error així.

error('My message.');

Utilitza la paraula clau throw per llançar excepcions .NET així com els valors del tipus d'excepció enumerat.

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

Utilitza la paraula clau throw sola dins d'un bloc de captura. En aquest cas, el throw es comporta com l'instrucció rethrow en C#. L'excepció original, el missatge d'excepció i el seu context, com la pila de trucades, es tornen a llançar i estan disponibles per a qualsevol sent de captura en el codi de crida.

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

Declaracions try, catch, finally i retry

Quan es produeix una excepció, el sistema primer la processa a través de la llista de captura del bloc d'intent més interior. Si el sistema troba un bloc de captura que gestiona el tipus d'excepció que s'ha produït, el control del programa salta a aquest bloc de captura . Si la llista de captures no té cap bloc que especifiqui l'excepció, el sistema passa l'excepció a la llista de captures del següent bloc de prova més interior. El sistema processa les sentències catch en la mateixa seqüència que apareixen al codi.

És una pràctica habitual que la primera sentència catch gestioni el valor d'enumeració Exception::Error . Una estratègia és que l'última sentència catch deixi el tipus d'excepció sense especificar. En aquest cas, l'última instrucció catch gestiona totes les excepcions que no gestiona cap instrucció catch anterior. Aquesta estratègia és adequada per a l'intent més extern ... bloquejos de captura .

Pots incloure una clàusula final opcional a Try... Declaracions de captura . La semàntica d'una clàusula finally és la mateixa que en C#. El sistema executa les instruccions de la clàusula finally quan el control surt del bloc try , ja sigui normalment o a través d'una excepció.

La sentència de reintent només pot aparèixer en un bloc catch . La sentència retry fa que el control salti a la primera línia de codi del bloc try associat. Utilitza la instrucció retry quan la causa de l'excepció es pugui fixar amb el codi del bloc de captura . La sentència retry dóna al codi del bloc try una altra oportunitat per tenir èxit. La sentència retry esborra tots els missatges que s'han escrit a l'Infolog des que el control del programa va entrar al bloc try .

Nota

Heu d'assegurar-vos que les declaracions de reintent no provoquin un bucle infinit. Com a pràctica recomanada, el bloc try hauria d'incloure una variable que podeu provar per esbrinar si esteu en un bucle.

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.
}

El controlador d'excepcions del sistema

Si cap instrucció de captura gestiona l'excepció, el gestor d'excepcions del sistema s'encarrega. El controlador d'excepcions del sistema no escriu a l'Infolog. Per tant, les excepcions no gestionades poden ser difícils de diagnosticar. Segueix totes aquestes directrius per oferir una gestió efectiva de les excepcions:

  • Tingueu un bloc try que contingui totes les vostres declaracions al fotograma més extern de la pila de trucades.
  • Tingueu un bloc de captura no qualificat al final de la vostra llista de captures més externes.
  • Eviteu llançar un valor d'enumeració d'excepció directament.
  • Llança el valor d'enum que retorna un dels següents mètodes a la classe Global : Global::error, Global::warning o Global::info. (Podeu ometre el prefix implícit Global::).
  • Quan detectis una excepció que l'Infolog no mostra, truca a la funció Global::info per mostrar-la.

Exception::CLRError, Exception::UpdateConflictNotRecovered i les excepcions del nucli del sistema són exemples d'excepcions que l'Infolog no mostra automàticament.

Excepcions i interoperabilitat CLR

Pots cridar classes i mètodes de Microsoft .NET Framework que resideixen en assemblies que gestiona el common language runtime (CLR). Quan el teu codi genera una instància .NET Framework System.Exception , pots detectar-la declarant una variable del tipus System.Exception per capturar qualsevol excepció .NET, o pots capturar un tipus específic d'excepció .NET utilitzant una de les seves classes derivades, com es mostra en l'exemple següent.

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);
}

Pots detectar excepcions .NET fent referència a Exception::CLRError. El teu codi pot obtenir una referència a la instància System.Exception cridant el mètode CLRInterop::getLastException .

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

Assegurar-se que es mostren excepcions

L'Infolog no mostra excepcions del tipus Exception::CLREror , perquè aquestes excepcions no es generen per una crida a un mètode com Global::error. Al bloc catch , el codi pot cridar Global::error per informar de l'excepció específica.

Mètodes de classe global

Aquesta secció descriu alguns mètodes de classe Global amb més detall. Aquests mètodes de classe inclouen Global::error, Global::info i Global::exceptionTextFallThrough.

Mètode Global::error

El codi següent mostra com es declara el mètode d'error .

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

El tipus retornat és el valor d'enumeració Exception::Error . El mètode error no genera cap excepció. Simplement proporciona un valor enum que pots utilitzar en una instrucció throw . La sentència throw llança l'excepció. Aquí teniu descripcions dels paràmetres del mètode d'error . Només es requereix el primer paràmetre.

  • SysInfoLogStr txt és un str del text del missatge. També pot ser una referència d'etiqueta, com ara strFmt("@SYS12345", strThingName).
  • L'URL helpUrl és una referència a la ubicació d'un article d'ajuda a l'Explorador d'aplicacions, com ara "KernDoc:\\\\Functions\\substr". El valor del paràmetre s'ignora si es proporciona _sysInfoAction.
  • SysInfoAction és una instància d'una classe que amplia la classe SysInfoAction. Les substitucions de mètodes que recomanem per a la classe secundària són el mètode description , el mètode run , el mètode pack i el mètode unpack .

Mètode global::info

Utilitza el mètode Global::info per mostrar el text a l'Infolog. En els programes, escriu-ho com a info("El meu missatge.");. Tot i que el mètode info retorna un valor enum Exception::Info , rarament vols llençar Exception::Info, perquè no ha passat res inesperat.

Mètode Global::exceptionTextFallThrough

De tant en tant, no voleu fer res dins del vostre bloc de captura . Tanmateix, el compilador X++ genera un avís si teniu un bloc catch buit. Per evitar aquest advertiment, crideu el mètode Global::exceptionTextFallThrough al bloc catch . El mètode no fa res, però satisfà el compilador i estableix explícitament la intenció.

Excepcions dins de les transaccions

Si es llança una excepció dins d'una transacció, la transacció es cancel·la automàticament (és a dir, es produeix una operació ttsAbort ). Aquest comportament s'aplica tant a les excepcions que es llencen manualment com a les excepcions que llança el sistema. Quan es llança una excepció dins d'un bloc de transacció ttsBegin-ttsCommit , cap instrucció catch dins d'aquest bloc de transacció pot processar l'excepció (tret que sigui un UpdateConflict o un DuplicateKeyException). En canvi, les sentències catch més internes que es troben fora del bloc de transaccions són les primeres sentències catch que es proven.

Per detectar UpdateConflict o DuplicateKeyException dins d'una transacció, especifica explícitament l'excepció a l'instrucció catch com aquesta, catch (Exception::DuplicateKeyException). Una sentència catch{} no pot capturar UpdateConflict o DuplicateKeyException dins d'una transacció.

La clàusula finally s'executa fins i tot en l'àmbit de la transacció.

Excepcions i using declaracions

L'abast de les excepcions no afecta la semàntica de les using afirmacions. La using declaració:

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

És semànticament idèntica a:

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

Exemples de gestió d'excepcions

Mostrar excepcions al registre d'informació

L'exemple de codi següent mostra excepcions a 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'.
**********/
}

Ús del mètode d'error per escriure informació d'excepció al registre d'informació

L'exemple de codi següent utilitza el mètode error per escriure informació d'excepció a 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'.
***/
}

Gestió d'un CLRError

L'exemple de codi següent gestiona una excepció CLRError .

// 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)
**********/
}

Ús d'una instrucció de reintent

L'exemple de codi següent utilitza una instrucció de reintent .

// 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]
**********/
}

Llançament d'una excepció dins d'una transacció

L'exemple de codi següent llança una excepció en un bloc de transaccions.

// 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.
**********/
}

Ús de Global::error amb un paràmetre SysInfoAction

Quan el vostre codi llança una excepció, pot escriure missatges a l'Infolog. Podeu fer que aquests missatges d'Infolog siguin més útils utilitzant la classe SysInfoAction .

En l'exemple següent, passes un paràmetre SysInfoAction al mètode Global::error . El mètode error escriu el missatge a l'Infolog. Quan l'usuari fa doble clic al missatge Infolog, s'executa el mètode SysInfoAction.run .

Al mètode d'execució , podeu escriure codi que ajudi a diagnosticar o solucionar el problema que ha causat l'excepció. L'objecte que passes al mètode Global::error es construeix a partir d'una classe que escrius i que amplia SysInfoAction.

L'exemple de codi següent es mostra en dues parts.

  • La primera part mostra una feina que crida el mètode Global::error i després llança el valor retornat. Passes una instància de la classe SysInfoAction_PrintWindow_Demo al mètode d'error .
  • La segona part mostra la classe SysInfoAction_PrintWindow_Demo .

Part 1: Crida a 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.");
    }
}

Part 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;
    }
}

Llista d'excepcions

A la taula següent es mostren els literals d'excepció que són els valors de l'enumeració Exception .

Literal d'excepció Descripció
Pausa L'usuari va prémer Break o Ctrl+C.
CLRError S'ha produït un error mentre s'utilitzava la funcionalitat CLR.
CodeAccessSecurity S'ha produït un error mentre s'utilitzava el mètode CodeAccessPermission.demand .
Error DDE S'ha produït un error mentre s'utilitzava la classe del sistema DDE .
Bloqueig S'ha produït un bloqueig de la base de dades, perquè diverses transaccions s'esperen mútuament.
Excepció DuplicateKeyException S'ha produït un error en una transacció que utilitza el control de simultaneïtat optimista. La transacció es pot tornar a provar (utilitzeu una instrucció de reintent al bloc catch ).
DuplicateKeyExceptionNotRecovered S'ha produït un error en una transacció que utilitza el control de simultaneïtat optimista. El codi no es tornarà a provar. Aquesta excepció no es pot detectar dins d'una transacció.
Error S'ha produït un error fatal. La transacció s'ha aturat.
Informació Aquest literal d'excepció conté un missatge per a l'usuari. No llencis una excepció d'informació .
Intern S'ha produït un error intern al sistema de desenvolupament.
Numèric S'ha produït un error mentre s'utilitzava la funció str2int, str2int64 o str2num .
Sequence
Conflicte d'actualització S'ha produït un error en una transacció que utilitza el control de simultaneïtat optimista. La transacció es pot tornar a provar (utilitzeu una instrucció de reintent al bloc catch ).
UpdateConflictNotRecovered S'ha produït un error en una transacció que utilitza el control de simultaneïtat optimista. El codi no es tornarà a provar. Aquesta excepció no es pot detectar en una transacció.
Advertiment S'ha produït un esdeveniment excepcional. Tot i que és possible que l'usuari hagi de prendre mesures, l'esdeveniment no és fatal. No llencis una excepció d'advertència .
Error de connexió SQL Excepció X++ S'ha produït un error durant l'execució de la consulta. La transacció es cancel·larà. Aquesta excepció no es pot detectar en una transacció.
Temps d'espera S'ha esgotat el temps d'execució de la consulta SQL. L'excepció no es pot detectar en una transacció. L'excepció es pot tornar a provar utilitzant una sentència de reintent al bloc catch.