C Sharpの基礎 - 例外処理

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

例外が発生する可能性がある場所では、Try-Catch構文により、その例外をキャッチして適切な処置を施す必要がある。


例外クラスの作成

例外クラスを作成する前に

例外クラスを作成する前に、注意すべき点が2つある。

  • 無闇に例外クラスを作成しない。
    .NET Frameworkが提供している例外クラスが使用できる場合は、特別な要件がない限りそれを使用する。

  • 例外クラスをエラー処理以外で使用しない。
    例外は、便利な条件分岐構文ではなく、エラー処理のための機構である。
    また、例外を使用せずに済むような状況(例えば、TryParseメソッドが使用できる等)であれば、使用しないに越したことはない。
    例外処理を多用する場合、パフォーマンスに悪影響を及ぼす可能性がある。


例外クラスの設計方針

  • 例外クラス名の末尾は、〜Exceptionとする。
    このように命名することにより、そのクラスが例外クラスであることが分かる。

  • 全ての例外クラスは、System.Exceptionを継承する。
    C#では、System.Exceptionクラスの派生クラスのみをスローすることができる。

    Exceptionクラスから派生していないオブジェクトをキャッチすることもできる。
    これは、.NET FrameworkのCLRの仕様では、どのようなオブジェクトでもスローすることができるからである。
    他の言語との相互運用することを考慮した結果、C#は任意のオブジェクトをキャッチすることができるようになっている。

  • 例外クラスの継承階層は、できる限り浅くする。
    例外クラスの継承階層を深くすると、基底の例外クラスをキャッチするには、様々なエラーに対処しなければならなくなる。
    また、新たな派生クラスを実装するごとに、基底となる例外クラスを捕捉する全ての動作を検証しなければならなくなる。

  • 全ての例外クラスはシリアル化可能であるべきである。
    以下のような場合、例外クラスをシリアル化する必要がある。
    • ソフトウェアの実行中にスローされた例外オブジェクトが、ソフトウェアドメインの境界を越える場合
    • 例外の内容をログに出力する場合

    上記のようなシーンにおいて、例外クラスを使用しない場合もあるが、
    シリアル化の対応は簡単に行うことができるため、対応することにより、例外クラスの堅牢性が増す。


シリアル化とは

シリアル化とは、オブジェクトをバイトストリームに変換する処理のことである。
オブジェクトの現在の状態をファイルに書き出すような処理を行う。

シリアル化されたオブジェクトは、テキストファイル等と同様、
ストレージに保存してソフトウェアの起動の時に読み込むこと、ネットワーク上に転送することができる。
ソフトウェアの設定ファイル等でよく使用されている。

また、シリアル化されたバイトストリームを元のオブジェクトに戻す処理のことを、逆シリアル化と呼ぶ。
シリアル化 (C#) | Microsoft Docs

例外クラスの作成

以下の例では、例外クラスを作成している。
例外クラスは、System.Exceptionクラスを継承して作成する。

以下の例のように、4つのコンストラクタは、与えられた引数を基底クラス(System.Exceptionクラス)のコンストラクタに渡す。

  • 基本の3つのコンストラクタを実装
    3番目のコンストラクタは、例外メッセージと共に、例外の原因となる別の例外への参照をinnerExceptionに設定して、
    例外クラスのインスタンスを初期化するためのものである。

  • 例外クラスのシリアル化対応
    SerializableAttribute属性と4番目のコンストラクタにより、例外クラスは、ソフトウェアのドメイン境界を越えることができる。
    SerializableAttribute属性がある全てのクラスは、フォーマッタを使用してシリアル化が可能になる。
    特に、例外クラスにSerializableAttribute属性がある場合、例外がソフトウェアのドメイン境界を超える時、必要に応じて自動的にシリアル化を行う。

    シリアル化されて、別のソフトウェアのドメインからスローされた例外オブジェクトを正しく受け取るためには、元の例外オブジェクトに戻す必要がある。
    これを、逆シリアル化と呼ぶ。
    これを行うのが、4番目のコンストラクタとなる。

    4番目のコンストラクタは、フォーマッタが呼び出すために存在するため、設計者が明示的に呼び出すことはない。
    また、呼び出されることを避けるため、protected修飾子を付加する。(フォーマッタは、アクセス修飾子に関係なく呼び出すことができる)

    逆シリアル化コンストラクタがpublic修飾子の場合、任意のコードから不正なデータを渡して例外クラスのインスタンスを生成できる。
    また、例外クラスの継承を禁止する(sealed修飾子を付加する)場合は、逆シリアル化コンストラクタにprivate修飾子を付加することもできる。
 using System;
 using System.Runtime.Serialization;
 
 [Serializable()]  // クラスがシリアル化可能であることを示す属性
 public class MyException : Exception
 {
    public MyException() : base()
    {
    }
 
    public MyException(string message) : base(message)
    {
    }
 
    public MyException(string message, Exception innerException) : base(message, innerException)
    {
    }
 
    // 逆シリアル化コンストラクタ
    // このクラスの逆シリアル化するために必須
    // アクセス修飾子をpublicにしないこと(詳細は後述)
    protected MyException(SerializationInfo info, StreamingContext context) : base(info, context)
    {
    }
 }



処理されていない例外をハンドルする

一般的に、例外が正しくキャッチ(トラップ)されていないというケースは多々ある。

その場合、ソフトウェアの実行中において、以下のような.NET Framework標準のエラーダイアログが表示されてしまう。
このエラーダイアログは、ユーザにとって理解しにくい。

Catch exception 01.gif


これを避けるために、.NET Framework標準のエラーダイアログを、自作のエラーダイアログに切り替えたいという要望も多い。

ここでは、処理されていない例外を1ヶ所に纏めてハンドルする方法を記載する。
この方法により、ハンドルしたメソッド内で独自に作成したエラーダイアログを表示すれば、前述の要望も実現可能である。

  • Application.ThreadExceptionイベントの活用
    処理されなかった例外をハンドルするには、WindowsフォームならApplicationクラス(System.Windows.Forms名前空間)の
    ThreadExceptionイベントをハンドルして処理する。
    このイベントは、Windowsフォームのメインスレッド(ApplicationクラスのRunメソッドにより実行されるアプリケーションのコンテキスト)内で
    発生した未処理の例外をハンドルするためのものである。

  • Thread.GetDomain().UnhandledExceptionイベントの活用
    では、上記のメイン・スレッド以外のコンテキスト上で発生した例外は、
    現在のアプリケーションドメイン(AppDomainクラス(System名前空間)のThread.GetDomainメソッドにより取得できる)の
    UnhandledExceptionイベントで出来る。
    例えば、マルチスレッド処理において、メインスレッド以外のスレッドで発生した例外やコンソールで発生した例外等は、
    全てこのUnhandledExceptionイベントをハンドルして処理すればよい。


以下の例では、上記2つのイベントを活用して、未処理の例外を実際に処理している。

 using System;
 using System.Windows.Forms;
 using System.Threading;
 
 namespace WindowsApplication1
 {
    public class Program
    {
       [STAThread]
       static void Main()
       {
          // ThreadExceptionイベント・ハンドラを登録する
          Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
 
          // UnhandledExceptionイベント・ハンドラを登録する
          Thread.GetDomain().UnhandledException += new UnhandledExceptionEventHandler(Application_UnhandledException);
 
          // メイン・スレッド以外の例外はUnhandledExceptionでハンドル
          //string buffer = "1"; char error = buffer[2];
 
          // ここで実行されるメインスレッドの例外はApplication_ThreadExceptionでハンドルできる
          Application.Run(new Form1());
       }
 
       // 未処理例外をキャッチするイベントハンドラ
       // (Windowsアプリケーション用)
       public static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
       {
          ShowErrorMessage(e.Exception, "Application_ThreadExceptionによる例外通知です。");
       }

       // 未処理例外をキャッチするイベントハンドラ
       // (主にコンソール用)
       public static void Application_UnhandledException(object sender, UnhandledExceptionEventArgs e)
       {
          Exception ex = e.ExceptionObject as Exception;
          if (ex != null)
          {
             ShowErrorMessage(ex, "Application_UnhandledExceptionによる例外通知です。");
          }
       }

       // ユーザー・フレンドリなダイアログを表示するメソッド
       public static void ShowErrorMessage(Exception ex, string extraMessage)
       {
          MessageBox.Show(extraMessage + @" \n――――――――\n\n" + @"エラーが発生しました。開発元にお知らせください\n\n" +
                          @"【エラー内容】\n" + ex.Message + "\n\n" + @"【スタックトレース】\n" + ex.StackTrace);
       }
    }
 }