[C#] 26. 例外処理(try ~ catch)する方法


Study / C#    作成日付 : 2019/07/16 00:59:34   修正日付 : 2021/09/09 19:10:28

こんにちは。明月です。


この投稿はC#で例外処理(try ~ catch)する方法に関する説明です。


我々がプログラムを作成したら予想できないエラーが発生する場合があります。

例えば、プログラムを作成する時に変数の値がない場合(nullエラー)か、正数の値に割り算をする時に整数を0で割る場合かの問題が発生することがあります。

using System;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 被除数の変数
      int dividend = 10;
      // 除数の変数
      int divisor = 0;
      
      // 割った値を出力する。
      Console.WriteLine(dividend / divisor);
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


プログラムの実行中でエラーが発生しました。

普通のプログラムの実行中でこんなエラーが発生すれば、エラーが発生した部分を飛び越えてプログラムが進めることではなく、プログラムがそのままに止まることになります。


もちろん、上の例みたいに宣言された変数の値が間違ったら、変数の値を直したら解決しますが、もし、ユーザが受け取ったデータが間違えたり、データベースの値が予想しない場合にはプログラムを止まることではなく、例外処理をするべきです。

つまり、エラーが発生してもプログラムが止まったり、終了されたらダメです。

using System;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 被除数の変数
      int dividend = 10;
      // 除数の変数
      int divisor = 0;
      // 例外処理
      try
      {
        // tryのスタック領域でエラーが発生すればcatch領域に飛び越える。
        Console.WriteLine(dividend / divisor);
      }
      catch (Exception e)
      {
        // エラーが発生すればエラー内容をコンソールに出力
        Console.WriteLine(e);
      }
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


結果をみれば割る領域でエラーが発生しますが、エラーメッセージをコンソールに出力して"Press any key..."のメッセージまでコンソールに出力されました。

つまり、エラーが発生してもプログラムが止まらなくて最後まで実行されることを確認できます。


エラー処理というのはエラーの区分によりcatchのスタック領域を分けることができます。

using System;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 被除数の変数
      int dividend = 10;
      // 除数の変数
      int divisor = 0;
      // 例外処理
      try
      {
        // tryのスタック領域でエラーが発生すればcatch領域に飛び越える。
        Console.WriteLine(dividend / divisor);
      }
      // 割り算エラー Exception
      catch(DivideByZeroException e)
      {
        // コンソール出力
        Console.WriteLine("DivideByZeroException");
      }
      // 最上位エラー Exception
      catch (Exception e)
      {
        // コンソール出力
        Console.WriteLine("Exception");
      }
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上のtry領域では割り算エラーが発生しましたので、DivideByZeroExceptionのスタック領域に移動します。

もし、try領域でDivideByZeroExceptionではないエラーが発生すれば、すべてのエラーをキャッチするException領域に移動します。

(ここでエラークラスの種類を分かる方法は始めにExceptionを作ってエラーを発生するとどのエラーが発生するかエラーメッセージで表示されます。始めの例を参考)


普通の一般コードならすべてのExceptionで処理しても構いないですが、発生するエラー別で処理することが違いなら上のソースみたいにエラー別で区分する作業が必要です。

つまり、Exceptionですべてのエラーをキャッチしても別に性能上で問題ありません。


そうなら我々がクラスを作成する時にクラスの特性上によりわざわざエラーを発生する時があります。

using System;

namespace Example
{
  // エラークラス
  class CharacterCountOver : Exception
  {
    // コンストラクタ。エラーが発生する時、出力するエラーメッセージ設定。
    public CharacterCountOver() : base("Exceeded character count")
    {

    }
  }
  // 例クラス
  class InputText
  {
    // メンバー変数
    private string data;
    // メンバー変数パラメータ
    public string Data
    {
      set
      {
        // 入力された文字数が10桁を超えたら
        if (value.Length > 10)
        {
          // CharacterCountOverエラーを発生する。
          throw new CharacterCountOver();
        }
        // そうではなければメンバー変数で値を格納
        data = value;
      }
      get
      {
        // メンバー変数を返却
        return data;
      }
    }
  }
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // クラスのインスタンスを生成
      InputText p = new InputText();
      try
      {
        // 値を入力、総12桁
        p.Data = "abcdefghijkl";
        // コンソール出力
        Console.WriteLine("OK!!");
      }
      // エラーが発生すれば
      catch (Exception e)
      {
        // コンソール出力
        Console.WriteLine(e);
      }
      // コンソール出力
      Console.WriteLine(p.Data);
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例では私がエラークラスを作成しました。このエラークラスは基本的にExceptionクラスを継承しました。継承しなかったらtry ~ catchで使えません。

そしてInputTextクラスのプロパティでthrowを使う時にエラーインスタンスを渡します。参考でthrowキーワードを使ったら強制エラーが発生して実行されているCall Stack historyで一番近いtry ~ catchに掛けます。もし、try ~ catchがなければ始めの例みたいにプログラムが止まります。

私は実装とおりMainのtry ~ catchでエラーがキャッチすることになります。

コンソール結果をみればCharacterCountOverが発生したことを確認できます。


ここでよくみれば、エラーは発生しましたが、コンソール出力OK!!は出力がされてないです。その意味はtryの領域ではエラーが発生すればエラーが発生したところでスタックが止まってcatch領域にジャンプしたことみたいに見えます。そのため、コンソールでOK!!が出力されないです。

その後のcatch領域からは正常に実行されることを確認できます。


それならここで一つの知りたいことができます。プログラムの制御文(if~else)みたいに使ったらプログラムをもっと有用に開発することではないか?

using System;
using System.Diagnostics;

namespace Example
{
  class Program
  {
    // 実行関数
    static void Main(string[] args)
    {
      // 奇数の合計
      int sum = 0;
      // 性能時間を測るクラス。
      Stopwatch sw = new Stopwatch();
      // 測定開始
      sw.Start();
      // 0から999までの繰り返し
      for (int i = 0; i < 1000; i++)
      {
        // 偶数の場合は飛び越える。
        if (i % 2 == 0)
        {
          continue;
        }
        // 奇数の値を足す。
        sum += i;
      }
      // 測定停止
      sw.Stop();
      // コンソール出力 - 処理時間
      Console.WriteLine("Control time - " + sw.ElapsedMilliseconds);
      // 結果出力
      Console.WriteLine("Result - " + sum);
      // 改行
      Console.WriteLine();
      // 結果変数初期化
      sum = 0;
      // エラークラスのインスタンスを生成
      Exception e = new Exception();
      // 測定開始
      sw.Start();
      // 0から999までの繰り返し
      for (int i = 0; i < 1000; i++)
      {
        try
        {
          // 偶数の場合はエラー発生
          if (i % 2 == 0)
          {
            throw e;
          }
          // 奇数の値を足す。
          sum += i;
        }
        catch (Exception)
        {

        }
      }
      // 測定停止
      sw.Stop();
      // コンソール出力 - 処理時間
      Console.WriteLine("Exception time - " + sw.ElapsedMilliseconds);
      // 結果出力
      Console.WriteLine("Result - " + sum);
      // 改行
      Console.WriteLine();
      // 任意のキーを押してください
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}


上の例はtry ~ catchを制御文みたいに使ってコードを作成しました。結果は性能が圧倒的に遅くなります。

つまり、このtry ~ catchはエラーをキャッチする役割しますが、パフォーマンスが凄く悪くなるので、出来れば制御文でエラーを取り除くのが主なポイントです。

参考でtry領域を囲むことだけでは性能には影響なくて、throwが発生してcatchに移動する時、インタラプトが掛けてしまうので遅くなります。


ここまでC#で例外処理(try ~ catch)する方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

#C#
最新投稿