基礎から学ぶC#入門 90日コース | 実践的なC#プログラミング - Day 51:例外処理

C# 90日で身につけるC#
スポンサーリンク
スポンサーリンク

Day 51 のゴールと全体像

Day 51 では、いよいよ 「例外処理」に踏み込みます。 キーワードはこの4つです。

  • try
  • catch
  • finally
  • throw

どれも C# ではおなじみの構文ですが、 「なんとなく使っている」状態から一歩進んで、 “なぜそう書くのか” を理解しながら使えるようになることを目指します。

例外処理は、アプリを「落ちないようにする」ための仕組みであり、 同時に「安全に失敗する」ための技術でもあります。 少しだけ緊張感のあるテーマですが、 肩の力を抜いて、雑誌を読むような気持ちで進めていきましょう。

例外とは何かをイメージする

「普通じゃない事態」が起きたときの合図

まずは「例外」という言葉のイメージから整理してみます。

プログラムを動かしていると、

  • ファイルが見つからない
  • ネットワークにつながらない
  • 数値の計算で 0 で割ってしまった
  • ユーザーが変な入力をした

など、「想定外」「異常系」の出来事が起こります。

こうした “普通の処理では扱えない事態” を、 C# では 例外(Exception) として表現します。

例外が発生すると、

  • その場の処理は中断される
  • 例外を「キャッチ」できる場所まで、処理がジャンプする

という流れになります。 この「ジャンプ」を、try / catch / finally / throw でコントロールしていくイメージです。

try と catch の基本構造

まずは形から覚える

例外処理の基本形は、次のような構文です。

try
{
    // 例外が発生するかもしれない処理
}
catch (Exception ex)
{
    // 例外が発生したときの処理
}
C#

とてもシンプルですが、 この2つが例外処理の「心臓部」です。

具体例:0 で割ってしまうケース

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("割り算をします。分子を入力してください。");
        string numeratorText = Console.ReadLine();

        Console.WriteLine("分母を入力してください。");
        string denominatorText = Console.ReadLine();

        try
        {
            // 文字列を整数に変換
            int numerator = int.Parse(numeratorText);
            int denominator = int.Parse(denominatorText);

            // ここで 0 で割ると例外が発生する可能性がある
            int result = numerator / denominator;

            Console.WriteLine($"結果: {result}");
        }
        catch (FormatException ex)
        {
            // 数字に変換できなかった場合
            Console.WriteLine("数字として解釈できない入力がありました。もう一度入力してください。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        catch (DivideByZeroException ex)
        {
            // 0 で割ろうとした場合
            Console.WriteLine("分母に 0 は指定できません。別の値を入力してください。");
            Console.WriteLine($"詳細: {ex.Message}");
        }

        Console.WriteLine("処理が終了しました。");
        Console.ReadLine();
    }
}
C#

ここで押さえておきたいポイント:

  • try ブロックには「例外が起きるかもしれない処理」をまとめて書きます。
  • catch ブロックには「例外が起きたときにどう振る舞うか」を書きます。
  • catch は複数書くことができ、 例外の種類ごとに違う対応をすることが可能です。

ユーザーの入力が多少おかしくても、 プログラム全体が「落ちて終わり」にならず、 “ちゃんと怒って、ちゃんと生き残る” ようになるわけですね。

finally の役割

「例外が起きても起きなくても、最後に必ずやること」

try / catch に続いて登場するのが finally です。

構文はこうなります。

try
{
    // 例外が発生するかもしれない処理
}
catch (Exception ex)
{
    // 例外が発生したときの処理
}
finally
{
    // 例外の有無にかかわらず、最後に必ず実行される処理
}
C#

finally は、

  • ファイルを閉じる
  • 接続を切る
  • 一時的なリソースを解放する

といった 「後片付け」 を書く場所として使われます。

具体例:ファイルを開いて読むとき

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        StreamReader reader = null;

        try
        {
            // ファイルを開く(存在しないと例外が発生する)
            reader = new StreamReader("data.txt");

            string content = reader.ReadToEnd();
            Console.WriteLine("ファイルの内容:");
            Console.WriteLine(content);
        }
        catch (FileNotFoundException ex)
        {
            Console.WriteLine("ファイルが見つかりませんでした。ファイル名を確認してください。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        catch (IOException ex)
        {
            Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        finally
        {
            // ここは例外が起きても起きなくても必ず実行される
            if (reader != null)
            {
                reader.Dispose(); // ファイルを閉じる
                Console.WriteLine("ファイルを閉じました。");
            }
        }

        Console.WriteLine("プログラムを終了します。");
        Console.ReadLine();
    }
}
C#

重要なポイント:

  • finally は「必ず実行される」ことが保証されているブロックです。
  • 例外が発生しても、catch の後に finally が呼ばれます。
  • 後片付けやリソース解放をここに書いておくと、 「例外が起きたせいでファイルが開きっぱなし」などの事故を防ぎやすくなります。

throw の意味と使いどころ

「自分から例外を投げる」という行為

ここまでの例では、 例外は「勝手に発生するもの」として扱ってきました。

しかし、実務では

「この状況はおかしいから、 ここで明示的に例外を投げておきたい」

という場面がよくあります。

そんなときに使うのが throw です。

具体例:引数チェックで throw する

using System;

class Calculator
{
    // 分母が 0 のときは例外を投げるメソッド
    public int Divide(int numerator, int denominator)
    {
        if (denominator == 0)
        {
            // 自分で例外を投げる
            throw new ArgumentException("分母に 0 は指定できません。", nameof(denominator));
        }

        return numerator / denominator;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var calc = new Calculator();

        try
        {
            int result = calc.Divide(10, 0);
            Console.WriteLine($"結果: {result}");
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine("不正な引数が渡されました。");
            Console.WriteLine($"パラメータ名: {ex.ParamName}");
            Console.WriteLine($"メッセージ  : {ex.Message}");
        }

        Console.ReadLine();
    }
}
C#

ここでのポイント:

  • throw は「例外を発生させる」ためのキーワードです。
  • new ArgumentException(...) のように、 例外オブジェクトを作って投げることで、 「何がおかしいのか」を呼び出し側に伝えることができます。

「握りつぶさない」ための throw

もうひとつ大事な使い方として、

catch で一度例外を受け取りつつ、 ログを残してから、再度 throw する

というパターンがあります。

try
{
    // 何かの処理
}
catch (Exception ex)
{
    // ログを残す
    Console.WriteLine($"エラー発生: {ex.Message}");

    // もう一度投げて、上位の層に判断を委ねる
    throw;
}
C#

このように、

  • 「ここでは対処しきれない」
  • 「もっと上の層で判断してほしい」

というときに、 例外を握りつぶさずに再度投げることで、 システム全体としての安全性を保ちやすくなります。

例外処理の基本テンプレート

よく使う形をひとつ覚えておく

最後に、初心者向けの「基本テンプレート」をまとめておきます。

using System;
using System.IO;

class Program
{
    static void Main(string[] args)
    {
        StreamReader reader = null;

        try
        {
            Console.WriteLine("読み込むファイル名を入力してください。");
            string fileName = Console.ReadLine();

            // 例外が発生するかもしれない処理
            reader = new StreamReader(fileName);

            string content = reader.ReadToEnd();
            Console.WriteLine("ファイルの内容:");
            Console.WriteLine(content);
        }
        catch (FileNotFoundException ex)
        {
            // 特定の例外に対する処理
            Console.WriteLine("指定されたファイルが見つかりませんでした。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        catch (IOException ex)
        {
            // 入出力全般の例外に対する処理
            Console.WriteLine("ファイルの読み込み中にエラーが発生しました。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        catch (Exception ex)
        {
            // 想定していない例外の最後の受け皿
            Console.WriteLine("予期しないエラーが発生しました。");
            Console.WriteLine($"詳細: {ex.Message}");
        }
        finally
        {
            // 後片付け(例外の有無にかかわらず実行される)
            if (reader != null)
            {
                reader.Dispose();
                Console.WriteLine("ファイルを閉じました。");
            }
        }

        Console.WriteLine("プログラムを終了します。");
        Console.ReadLine();
    }
}
C#

このテンプレートには、

  • try:例外が起きるかもしれない処理
  • catch:例外ごとの対処
  • catch (Exception ex):最後の受け皿
  • finally:後片付け

という、例外処理の基本要素がすべて詰まっています。

Day 51 のまとめ

Day 51 では、

  • 例外とは何か → 「普通じゃない事態」を表す仕組み
  • try / catch → 例外が起きるかもしれない処理と、その対処を分けて書く構文
  • finally → 例外の有無にかかわらず、最後に必ず実行したい処理を書く場所
  • throw → 自分で例外を発生させたり、 一度受け取った例外を再度投げたりするためのキーワード

を、具体的なコード例とともに学びました。

例外処理は、 「バグを隠すためのもの」ではなく、 「失敗をきちんと扱うための技術」です。

ぜひ、

  • 自分で小さなサンプルを書いて、わざと例外を起こしてみる
  • catch の中でメッセージを変えてみる
  • finally にログ出力を追加してみる

といった小さな実験を通して、 例外処理を「怖いもの」ではなく、 頼れる相棒のような存在として、 少しずつ手になじませていってください。

タイトルとURLをコピーしました