🐙

【超ド基礎】例外処理の基本と王道(サンプル例:C#)

2023/02/14に公開

例外処理の目的

システムにはエラーが存在する。
エラーとはシステム上の欠陥が見つかった状態のことを言う。
システムを使っていてエラーが起こると、システムが落ちたり固まったりして使用に耐えない。

よってエラーが起こった場合に、システムが落ちたり固まったりしないように対策が必要。
そのために存在する考えや対策のことを「例外処理」という。

つまり例外処理の目的は
「システムが落ちないように対策をすること」と言える。
また、
「不自然なデータが入ってこないように制御すること」なども例外処理で行う。

目次

例外処理の基本サンプル

try
{
    // ファイルを開く
    FileStream fs = new FileStream("example.txt", FileMode.Open);

    // ファイルからテキストを読み込む
    StreamReader reader = new StreamReader(fs);
    Console.WriteLine(reader.ReadToEnd());

    // リソースを解放する
    reader.Close();
    fs.Close();
}
catch (FileNotFoundException ex)
{
    // ファイルが見つからなかった場合のエラーメッセージ
    Console.WriteLine("ファイルが見つかりませんでした: " + ex.FileName);
}
catch (Exception ex)
{
    // その他の例外が発生した場合のエラーメッセージ
    Console.WriteLine("予期しないエラーが発生しました: " + ex.Message);
}
finally
{
    // 開放漏れリソースがあれば解放する
    reader?.Close();
    fs?.Close();
}

基本的にはtry catch構文を利用する。
エラーをキャッチする部分がtry{}の中。
エラーがキャッチされた場合、catch{}部分へ処理が移動する。
エラーが起きたときでもそうでないときでも、必ず行いたい処理はfinaly{}部分へ書く。

例外発生時にはオブジェクトが生成される

エラーが起きた場合、内部的にエラーオブジェクトがインスタンス化される。
(C#.Netではそういう仕様になってるらしい。)
エラーの種類によって、生成されるオブジェクトの種類が変わる。

割り算のときの数値が0のときのエラー、ファイルが存在しないエラー、文字が数値に変換できないエラー、その他のエラー、など様々なエラーの種類があり、それぞれ別のクラスのインスタンスとしてエラー時に生成される。

エラーの種類 エラーのクラス
割り算のときの数値が0のときのエラー System.DivideByZeroException
ファイルが存在しないエラー System.IO.FileNotFoundException
文字が数値に変換できないエラー System.FormatExceptionSystem.FormatException
その他のエラー System.Exception

エラークラスが分かれている理由は、それぞれのエラーが発生したときに、それぞれ別のエラーメッセージを出したり、それぞれ別の処理を行う必要があるため。

例外処理の流れ

ざっくりとした流れ

  1. try{}の通常処理が走る
  2. エラーが発生した場合に、該当するエラークラスのインスタンスが生成される
  3. 該当するエラークラスのcatch{}部分へ移動する
  4. finalyへ移動する

より詳細な流れ

  1. try{}部分の処理が1行ずつ走る
  2. エラーが発生した行を通ってそこでエラーが起きた場合にエラーのインスタンス(オブジェクト)が生成される。
  3. (エラー発生部以降の処理は行われない)
  4. 該当するエラークラスのcatch{}部分へ移動する。
  5. finaly{}部分へ移動する。(3.によりプロセス漏れなどがある場合、ここで必ずプロセスの開放などを行うように利用することが多い。)

補足:例外処理が関数内でネストしている場合は、よりエラー箇所に近い方でキャッチされる。

例外を自分で発生させる

上記は主にプログラム側でのエラーに対応する書き方。
これ以外にも、意図的に自分で例外を発生させてエラーと判定させることができる。

ユーザーが入力した値が明らかにおかしいものである場合などに、エラーインスタンスを生成し、意図的に例外処理を発生させる。

以下サンプルコード


try
{
    int width = 0;
    int height = 0;
    Console.Write("幅を入力してください: ");
    width = int.Parse(Console.ReadLine());
    Console.Write("高さを入力してください: ");
    height = int.Parse(Console.ReadLine());

    if (width <= 0 || height <= 0)
    {
        // 幅または高さが0以下の場合に例外をスローする
        throw new ArgumentException("幅と高さは0より大きい数値を入力してください");
    }

    int area = width * height;
    Console.WriteLine("面積は {0} です", area);
}
catch (FormatException ex)
{
    // 数値以外の値が入力された場合のエラーメッセージ
    Console.WriteLine("数値以外の値が入力されました");
}
catch (ArgumentException ex)
{
    // 幅または高さが0以下の場合のエラーメッセージ
    Console.WriteLine(ex.Message);
}
catch (Exception ex)
{
    // その他の例外が発生した場合のエラーメッセージ
    Console.WriteLine("予期しないエラーが発生しました: " + ex.Message);
}
finally
{
    Console.WriteLine("処理が完了しました");
}

width <= 0 || height <= 0の条件のときに
ArgumentExceptionクラスのインスタンスを作成。
(引数に文字列でMessage("幅と高さは0より大きい数値を入力してください")を入れている。)

例外がインスタンス化されたのでcatch部分へ移動して、例外処理が行われる。

まとめ

  • try catch構文を使う
  • エラー時はエラーインスタンスが生成される
  • 自分でエラーインスタンスを生成することも可能(throw new クラス名)
  • インスタンスが生成されたら該当するcatch部へ移動
  • finalyは正常時でも例外時でも必ず通る

Discussion