X++ 例外処理

この記事では、X++ の例外処理について説明します。 スローを使用してエラーを処理します。try...catchfinally再試行ステートメントを使用して例外を生成して処理します。

例外はプログラム実行の順序から離れて規制ジャンプします。 try...catch ブロックとスローされる例外の種類によって、プログラムの実行が再開される命令が決まります。 例外は、Exception 列挙の値、または .NET の System.Exception クラスまたは派生クラスのインスタンスによって表されます。 よくスローする例外の 1 つは 、Exception::error 列挙型値です。 一般的な方法は、例外をスローする前に、Infolog に診断情報を書き込む方法です。

Global::error メソッドは多くの場合、情報ログに診断情報を書き込む最善の方法です。 たとえば、メソッドは無効な入力パラメーター値を受け取る場合があります。 この場合、メソッドは例外をスローして、このエラー状況を処理するためのロジックを含む catch コード ブロックにすぐにコントロールを転送します。 例外がスローされたときに制御を受け取る catch ブロックの場所を必ずしも把握する必要はありません。

throw statements

throw キーワードを使用して、例外列挙値をスローします。 たとえば、次のステートメントは例外エラーをスローします。

throw Exception::error;

列挙値をスローする代わりに、 Global::error メソッドの出力を スローのオペランドとして使用します。

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

Global::error メソッドは、対応するテキストにラベルを自動的に変換します。 この機能は、ローカライズ可能なコードを書くのに役立ちます。

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

Global:: プレフィックスを指定せずに、Global クラスで静的メソッドを呼び出すことができます。 たとえば、次のように Global::error メソッドを呼び出すことができます。

error('My message.');

throw キーワードを使用して、.NET 例外と列挙例外型の値をスローします。

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

catch ブロック内で throw キーワードを単独で使用します。 この場合、 throw は C# の 再スロー ステートメントのように動作します。 元の例外、例外メッセージ、および呼び出し履歴などのコンテキストは再スローされ、呼び出し元のコードの catch ステートメントで使用できます。

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

try ステートメント、catch ステートメント、最後に retry ステートメント

例外が発生すると、システムは最初に最も内側の try ブロックの catch リストを介して処理します。 発生した例外の種類を処理する catch ブロックがシステムによって検出された場合、プログラム制御はその catch ブロックにジャンプします。 catch リストに例外を指定するブロックがない場合は、システムは、次の一番内側の try ブロックのcatch リストに例外を渡します。 catch ステートメントは 、コードに表示されるのと同じシーケンスで処理されます。

最初の catch ステートメントで Exception::Error 列挙値を処理することが一般的です。 1 つの方法は、最後の catch ステートメントの例外タイプを未指定のままにすることです。 この場合、前回の catch ステートメントは、前のいずれかの catch ステートメントで処理されていないすべての例外を処理します。 この戦略は、最も外側の try ... catchブロックに適しています。

try..に省略可能な finally 句を含めることができます。catch ステートメント。 finally 節のセマンティクスは、C# と同じです。 通常どおり、または例外を使用して、コントロールが try ブロックを離れると、システムは finally 句のステートメントを実行します。

retry ステートメントは、catch ブロックでのみ表示できます。 retry ステートメントは、関連付けられている try ブロックの最初のコード行までコントロールをジャンプさせます。 catch ブロック内のコードによって例外の原因を修正できる場合は、再試行ステートメントを使用します。 retry ステートメントは、try ブロック内のコードに成功するための別の機会を与えます。 retry ステートメントは、プログラム コントロールが try ブロックに入ってから情報ログに書き込まれたすべてのメッセージを消去します。

Note

再試行ステートメントにより、無限ループが発生しないことを確認する必要があります。 ベスト プラクティスとして、試行ブロックは、ループ中であるかを確認するテストができる変数を含める必要があります。

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

システム例外ハンドラー

catch ステートメントが例外を処理しない場合、システム例外ハンドラーはそれを処理します。 システム例外ハンドラーは、情報ログを作成できません。 したがって、ハンドルされない例外を診断することは難しい可能性があります。 次のすべてのガイドラインに従って、効果的な例外処理を提供します。

  • 呼び出しスタックの最も外側のフレームで、すべての明細書を含む try ブロックがあります。
  • 最も外側の catch リストの最後に非修飾の catch ブロックがあります。
  • 例外列挙値を直接スローするのは避けてください。
  • Global クラスの次のいずれかのメソッドが返す列挙値をスローします:Global::errorGlobal::warning、または Global::info。 (暗黙的 Global:: 接頭語を省略することができます)。
  • 情報ログに表示されない例外をキャッチしたら、 Global::info 関数を呼び出して表示します。

Exception::CLRErrorException::UpdateConflictNotRecovered、およびシステム カーネルの例外は、Infolog に自動的に表示されない例外の例です。

例外および CLR 相互運用

共通言語ランタイム (CLR) が管理するアセンブリに存在する Microsoft .NET Framework のクラスとメソッドを呼び出すことができます。 コードが .NET Framework System.Exception インスタンスをスローする場合は、 System.Exception 型の変数を宣言して .NET 例外をキャッチすることでキャッチできます。または、次の例に示すように、その派生クラスのいずれかを使用して特定の .NET 例外型をキャッチできます。

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

.NET 例外をキャッチするには、 Exception::CLRError を参照します。 コードでは、CLRInterop::getLastException メソッドを呼び出すことによって、System.Exception インスタンスへの参照を取得できます。

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

例外が表示されるようにする

これらの例外は Global::error などのメソッドの呼び出しによって生成されないため、情報ログには Exception::CLRError 型の例外は表示されません。 catch ブロックでは、コードで Global::error を呼び出して特定の例外を報告できます。

グローバル クラス メソッド

このセクションでは、いくつかのグローバルクラス メソッドについて詳しく説明します。 これらのクラスのメソッドには、Global::errorGlobal::infoGlobal::exceptionTextFallThrough が含まれます。

Global::error method

次のコードは、error メソッドの宣言方法を示しています。

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

戻り値の型は、Exception::Error 列挙値です。 error メソッドは例外をスローしません。 throw ステートメントで使用できる列挙値を提供するだけです。 throw ステートメントは例外をスローします。 エラーメソッドのパラメーターの説明を次に示します。 最初のパラメーターのみ必要です。

  • SysInfoLogStr txt は、メッセージ テキストの str です。 また、strFmt("@SYS12345", strThingName) などのラベルの参照にもなります。
  • URL helpUrl は、アプリケーション エクスプローラーのヘルプ記事の場所への参照です ("KernDoc:\\\\Functions\\substr")。 _sysInfoAction が提供された場合、このパラメータは無視されます。
  • SysInfoAction は、SysInfoAction クラスを拡張するクラスのインスタンスです。 description メソッド、run メソッド、pack メソッド、および unpack メソッドは、子クラスに対して推奨されるメソッドのオーバーライドです。

Global::info method

Global::info メソッドを使用して、情報ログにテキストを表示します。 プログラムでは、 info("My message.") として書き込みます。 info メソッドは Exception::Info 列挙型の値を返しますが、予期しない何も発生しないため、Exception::Info をスローすることはめったにありません。

Global::exceptionTextFallThrough method

場合によっては、catch ブロック内で何もしない場合があります。 ただし、空の catch ブロックがある場合、X++ コンパイラは警告が生成されます。 この警告を避けるためには、catch ブロック内の Global::exceptionTextFallThrough メソッドを呼び出します。 このメソッドは何もしませんが、コンパイラを満たし、意図を明示的に述べます。

トランザクション内の例外

例外がトランザクションの内部でスローされる場合は、トランザクションが自動的にキャンセル (つまり、ttsAbort 操作が発生) されます。 この動作は、手動でスローされる例外とシステムがスローする例外の両方に適用されます。 ttsBegin-ttsCommit トランザクション ブロック内で例外がスローされるとき、そのトランザクション ブロック内の catch ステートメントは例外を処理できます。(ただし、UpdateConflict または DuplicateKeyException 以外の場合に限ります)。 代わりに、トランザクション ブロックの外部にある最も内側の catch ステートメントが、最初にテストされる catch ステートメントです。

トランザクション内で UpdateConflict または DuplicateKeyException をキャッチするには、次のように catch ステートメントで例外を明示的に指定 catch (Exception::DuplicateKeyException)。 一般的な catch-all ステートメント catch{} は、トランザクション内で発生した UpdateConflict または DuplicateKeyException をキャッチできません。

最終句は、トランザクション スコープ内でも実行されます。

例外および using ステートメント

例外スコープは、 using ステートメントのセマンティクスには影響しません。 using ステートメント:

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

意味的に次のものと同一です:

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

例外処理の例

情報ログに例外を表示

次のコード例は、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'.
**********/
}

エラー メソッドを使用して例外情報を情報ログに記述

次のコード例では、error メソッドを使用して例外情報を 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'.
***/
}

CLRError の処理

次のコード例は、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)
**********/
}

再試行ステートメントの使用

次のコード例では、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]
**********/
}

トランザクション内での例外のスロー

次のコード例は、トランザクション ブロックに例外をスローします。

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

SysInfoAction パラメーターでの Global::error の使用

コードが例外をスローすると、情報ログにメッセージを書き込むことができます。 SysInfoAction クラスを使用すると、これらの情報ログ メッセージをさらに便利にすることができます。

次の例では、Global::error メソッドに SysInfoAction パラメーターを渡します。 error メソッドは、情報ログにメッセージを書き込みます。 ユーザーが Infolog メッセージをダブルクリックすると、 SysInfoAction.run メソッドが実行されます。

run メソッドでは、診断を支援したり、例外が発生した問題を解決したりするコードを書き込めます。 Global::error メソッドに渡すオブジェクトは、SysInfoAction を拡張する記述したクラスから構築されます。

次のコード例は 2 つの部分で示されています。

  • 最初の部分は、Global::error メソッドを呼び出して、返された値をスローするジョブを示しています。 SysInfoAction_PrintWindow_Demo クラスのインスタンスをエラー メソッドに渡します。
  • 2 番目の部分は、SysInfoAction_PrintWindow_Demo クラスを示します。

パート 1: 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.");
    }
}

パート 2: 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;
    }
}

例外のリスト

次のテーブルは、例外 列挙型の値である例外リテラルを示しています。

Exception literal Description
Break ユーザーは Break または Ctrl+C を押しました。
CLRError CLR 機能の使用中にエラーが発生しました。
CodeAccessSecurity CodeAccessPermission.demand メソッドを使用中にエラーが発生しました。
DDEerror DDE システム クラスを使用中にエラーが発生しました。
Deadlock 複数のトランザクションが相互に待機しているため、データベースのデッドロックが発生しました。
DuplicateKeyException オプティミスティック同時実行制御を使用しているトランザクションでエラーが発生しました。 トランザクションは再試行できます (catch ブロックで retry ステートメントを使用します)。
DuplicateKeyExceptionNotRecovered オプティミスティック同時実行制御を使用しているトランザクションでエラーが発生しました。 このコードは再試行されません。 この例外は、トランザクション内では検出されません。
Error 致命的なエラーが発生しました。 トランザクションは停止されました。
Info この例外リテラルは、ユーザーのメッセージを保持します。 情報例外をスローしないでください。
Internal 開発システムで内部エラーが発生しました。
Numeric str2intstr2int64、または str2num 機能を使用中にエラーが発生しました。
Sequence
UpdateConflict オプティミスティック同時実行制御を使用しているトランザクションでエラーが発生しました。 トランザクションは再試行できます (catch ブロックで retry ステートメントを使用します)。
UpdateConflictNotRecovered オプティミスティック同時実行制御を使用しているトランザクションでエラーが発生しました。 このコードは再試行されません。 この例外は、トランザクション内では検出されません。
Warning 例外的なイベントが発生しました。 ユーザーはアクションの実行をする必要がありますが、イベントは致命的ではありません。 警告例外をスローしないでください。
X++ の SQL 接続エラー例外 クエリ実行時にエラーが発生しました。 トランザクションはキャンセルされます。 この例外は、トランザクション内では検出されません。
Timeout SQL クエリの実行がタイムアウトしました。トランザクション内では例外をキャッチできません。 catch ブロックで retry ステートメントを使用すると、例外を再試行できます。