演習 - 特定の例外の種類をキャッチする

完了

このモジュールの前半では、C# アプリケーションによってキャッチされた例外オブジェクトが例外クラスのインスタンスであることを学習しました。 一般に、コードは次のいずれかを catch します。

  • System.Exception基底クラスのインスタンスである例外オブジェクト。
  • 基底クラスから継承する例外型のインスタンスである例外オブジェクト。 たとえば、 InvalidCastException クラスのインスタンスです。

例外プロパティを調べる

System.Exception は、すべての派生例外型が継承する基底クラスです。 各例外の種類は、特定のクラス階層を通じて基底クラスから継承されます。 たとえば、 InvalidCastException のクラス階層は次のとおりです。

Object
    Exception
        SystemException
            InvalidCastException

Exceptionから継承する例外クラスのほとんどは、追加の機能を追加しません。これらは単にExceptionから継承します。 そのため、 Exception クラスのプロパティを調べることで、ほとんどの例外と、コードで例外を使用する方法を理解できます。

Exception クラスのプロパティを次に示します。

  • Data: Data プロパティは、キーと値のペアの任意のデータを保持します。
  • HelpLink: HelpLink プロパティは、例外の原因に関する広範な情報を提供するヘルプ ファイルへの URL (または URN) を保持するために使用できます。
  • HResult: HResult プロパティを使用して、特定の例外に割り当てられているコード化された数値にアクセスできます。
  • InnerException: InnerException プロパティを使用すると、例外処理中に一連の例外を作成して保持できます。
  • メッセージ: Message プロパティは、例外の原因に関する詳細を提供します。
  • ソース: Source プロパティを使用して、エラーの原因となるアプリケーションまたはオブジェクトの名前にアクセスできます。
  • StackTrace: StackTrace プロパティには、エラーが発生した場所を特定するために使用できるスタック トレースが含まれています。
  • TargetSite: TargetSite プロパティを使用して、現在の例外をスローするメソッドを取得できます。

例外プロパティ、基底クラス、継承のこの検査に少し圧倒されている場合は問題ありません。 コード内の例外をキャッチし、例外のプロパティにアクセスする方が、例外と例外のプロパティのしくみを説明するよりも簡単です。

このモジュールでは、例外のメッセージ プロパティを使用して、アプリケーションのユーザー インターフェイスで例外を報告することに重点を置きます。

例外オブジェクトのプロパティにアクセスする

例外オブジェクトとそのプロパティを理解したら、コーディングを開始します。

  1. Program.cs ファイルを次のように更新します。

    try
    {
        Process1();
    }
    catch
    {
        Console.WriteLine("An exception has occurred");
    }
    
    Console.WriteLine("Exit program");
    
    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch
        {
            Console.WriteLine("Exception caught in Process1");
        }
    }
    
    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
    
        Console.WriteLine(float1 / float2);
        Console.WriteLine(number1 / number2);
    }
    
  2. 少し時間をかけてコードを確認してください。

    これは、前のユニット (チャレンジ アクティビティのソリューション コード) で確認したコードと同じです。 WriteMessage メソッドの実行中に例外がスローされることがわかります。 また、 Process1 メソッドで例外がキャッチされていることもわかります。 このコードを使用して、例外オブジェクトと特定の例外の種類を調べます。

  3. 次のように、Process1 メソッドを更新します。

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  4. 更新プログラムを調べるには 1 分かかります。

    更新された catch 句が、ex という名前のオブジェクト内のException クラスのインスタンスをキャッチしていることに注意してください。 また、Console.WriteLine() メソッドがオブジェクトの Message プロパティにアクセスし、コンソールにエラー メッセージを表示するためにexを使用していることにも注意してください。

    catch句は引数なしで使用できますが、この方法はお勧めしません。 引数を指定しない場合、すべての例外の種類がキャッチされ、それらの間でデクサーすることはできません。

    一般に、コードが復旧する方法を認識している例外のみをキャッチする必要があります。 したがって、 catch 句では、 System.Exceptionから派生したオブジェクト引数を指定する必要があります。 例外の種類は、可能な限り具体的である必要があります。 これは、例外ハンドラーが解決できない例外をキャッチしないようにするのに役立ちます。 この演習の後半で、特定の例外の種類をキャッチするようにコードを更新します。

  5. [ファイル] メニューの [保存] をクリックします。

  6. 次のコード行にブレークポイントを設定します。

    Console.WriteLine($"Exception caught in Process1: {ex.Message}");
    
  7. [実行] メニューの [デバッグの開始] を選択します。

    コードの実行はブレークポイントで一時停止する必要があります。

  8. マウス カーソルを exの上に置きます。

    IntelliSense には、前に調べたのと同じ例外プロパティが表示されます。

  9. 例外オブジェクトの exを説明する情報を調べるには 1 分かかります。

    例外が System.DivideByZeroException 例外の種類であり、 Message プロパティが Attempted to divide by zero. に設定されていることに注意してください。

    StackTrace プロパティは、エラーが発生したメソッドと行番号と、エラーの原因となったメソッド呼び出し (および行番号) のシーケンスを報告します。

  10. [ デバッグ] ツール バーの [ 続行] を選択します。

  11. コンソールの出力を調べるのに 1 分かかります。

    アプリケーションによって生成される出力には、例外の Message プロパティが含まれていることに注意してください。

    ∞
    Exception caught in Process1: Attempted to divide by zero.
    Exit program
    

特定の例外の種類をキャッチする

キャッチする例外の種類がわかったら、その特定の例外の種類を処理するように catch 句を更新できます。

  1. 次のように、Process1 メソッドを更新します。

    static void Process1()
    {
        try
        {
            WriteMessage();
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in Process1: {ex.Message}");
        }
    }
    
  2. コードを保存し、デバッグ セッションを開始します。

  3. 更新されたアプリケーションがコンソールに同じメッセージを報告していることに注意してください。

    報告されるメッセージは同じですが、重要な違いがあります。 Process1 メソッドは、処理する準備ができている特定の型の例外のみをキャッチします。

  4. 別の例外の種類を生成するには、 WriteMessage メソッドを次のように更新します。

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        Console.WriteLine(float1 / float2);
        // Console.WriteLine(number1 / number2);
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    
  5. checked ステートメントの使用に注意してください。

    ある整数型の値を別の整数型に割り当てる整数型の計算を実行する場合、結果はオーバーフロー チェック コンテキストに依存します。 checkedコンテキストでは、変換元の値が変換先の型の範囲内にある場合、変換は成功します。 それ以外の場合は、OverflowException が投げられます。 チェックされていないコンテキストでは、変換は常に成功し、次のように進みます。

    • ソースの種類が変換先の型よりも大きい場合は、その "余分な" 最上位ビットを破棄することで、ソース値が切り捨てられます。 結果は変換先の型の値として扱われます。

    • ソースの種類が変換先の型よりも小さい場合、変換元の値は、コピー先の型と同じサイズになるように、符号拡張またはゼロ拡張のいずれかになります。 署名拡張機能は、ソースの種類が署名されている場合に使用されます。ソース型が符号なしの場合は、0-extension が使用されます。 結果は変換先の型の値として扱われます。

    • ソースの種類がコピー先の型と同じサイズの場合、ソース値は変換先の型の値として扱われます。

    checkedコード ブロック内にない整数型の計算は、unchecked コード ブロック内にあるかのように扱われます。

  6. コードを保存し、デバッグ セッションを開始します。

  7. 新しい例外の種類は、Process1 メソッド内ではなく、最上位レベルのステートメントのcatch句によってキャッチされます。

    アプリケーションは、次のメッセージをコンソールに出力します。

    ∞
    An exception has occurred
    Exit program
    

    Process1catch ブロックは実行されません。 これは、必要な動作です。 コードが処理する準備ができている例外のみをキャッチします。

コード ブロックで複数の例外をキャッチする

この時点で、1 つのコード ブロックで複数の例外が発生するとどうなるか疑問に思うかもしれません。 コードでは、発生するたびに各例外が catch されますか?

  1. 次のように、WriteMessage メソッドを更新します。

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        Console.WriteLine(float1 / float2);
        Console.WriteLine(number1 / number2);
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    
  2. 次のコード行で、 WriteMessage() メソッド内にブレークポイントを設定します。

    Console.WriteLine(float1 / float2);
    
  3. コードを保存し、デバッグ セッションを開始します。

  4. 一度に 1 行ずつコードをステップ実行し、コードが最初の例外を処理した後に何が起こるかを確認します。

    最初の例外が発生すると、例外を処理できる最初の catch 句に制御が渡されます。 2 番目の例外を生成するコードに到達することはありません。 つまり、一部のコードは実行されません。 これは深刻な問題につながる可能性があります。

  5. 複数の例外を管理する方法と、コードで複数の例外を管理したくないタイミング/理由を少し検討してください。

    このモジュールの前半で、例外は可能な限り発生する場所に近い場所でキャッチする必要があることを学習しました。 これを念頭に置いて、独自のtry-catchを使用して例外をキャッチするようにWriteMessageメソッドを更新することもできます。 例えば次が挙げられます。

    static void WriteMessage()
    {
        double float1 = 3000.0;
        double float2 = 0.0;
        int number1 = 3000;
        int number2 = 0;
        byte smallNumber;
    
        try
        {
            Console.WriteLine(float1 / float2);
            Console.WriteLine(number1 / number2);
        }
        catch (DivideByZeroException ex)
        {
            Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}");
        }
        checked
        {
            smallNumber = (byte)number1;
        }
    }
    

    OverflowExceptionを発生させるコードを、WriteMessage() メソッド内の別のtry-catchにラップすることもできます。

    checked
    {
        try
        {
            smallNumber = (byte)number1;
        }
        catch (OverflowException ex)
        {
            Console.WriteLine($"Exception caught in WriteMessage: {ex.Message}");
        }  
    }
    
  6. どのような条件下で、後続の例外をキャッチすることは望ましくありませんか?

    メソッド (またはコード ブロック) が 2 つの部分から構成されるプロセスを完了している場合を考えてみましょう。 プロセスの 2 番目の部分が、完了する最初の部分に依存しているとします。 プロセスの最初の部分が正常に完了できない場合、プロセスの 2 番目の部分に進む意味はありません。 この場合、多くの場合、より大きなプロセスの残りの部分または一部を試みることなく、エラー状態を説明するメッセージをユーザーに表示することをお勧めします。

コード ブロック内の個別の例外の種類をキャッチする

データのバリエーションによって、さまざまな種類の例外が発生する場合があります。

  1. ブレークポイントをクリアし、Program.cs ファイルの内容を次のコードに置き換えます。

    // inputValues is used to store numeric values entered by a user
    string[] inputValues = new string[]{"three", "9999999999", "0", "2" };
    
    foreach (string inputValue in inputValues)
    {
        int numValue = 0;
        try
        {
            numValue = int.Parse(inputValue);
        }
        catch (FormatException)
        {
            Console.WriteLine("Invalid readResult. Please enter a valid number.");
        }
        catch (OverflowException)
        {
            Console.WriteLine("The number you entered is too large or too small.");
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    
  2. このコードを確認するには少し時間がかかります。

    まず、 inputValuesという名前の文字列配列を作成します。 配列内のデータは、数値を入力するように指示されたユーザーが入力した入力値を表すことを目的としています。 入力した値によっては、異なる例外の種類が発生する場合があります。

    コードでは、 int.Parse メソッドを使用して文字列 "input" 値を整数に変換していることに注意してください。 int.Parse コードは、try コード ブロック内に配置されます。

  3. 次のコード行にブレークポイントを設定します。

    int numValue = 0;
    
  4. コードを保存し、デバッグ セッションを開始します。

  5. 一度に 1 行ずつコードをステップ実行し、異なる例外の種類がキャッチされていることがわかります。

まとめ

このユニットで覚えておく必要があるいくつかの重要な点を次に示します。

  • catch句は、特定の例外の種類をキャッチするように構成する必要があります。 たとえば、 DivideByZeroException 例外の種類です。
  • 例外オブジェクトのプロパティは、 catch ブロック内でアクセスできます。 たとえば、 Message プロパティを使用して、アプリケーション ユーザーに問題を通知できます。
  • 複数の例外の種類をキャッチする必要がある場合は、2 つ以上の catch 句を指定できます。