header

■例外処理

本節では例外についてまとめてみます。
例外とは、コンパイル時に発見される問題ではなく、
実行時に起こるエラー(異常)のことです。

C#に限らず、
例外処理の仕組みがある言語を利用する際は
積極的にこの機能を使うことで
実行時のエラーに対処するコードを
簡潔にスッキリ書くことができます。

例えば、連続する10個の処理があるとします。
1つ1つの処理ごとにパラメータや戻り値、
処理そのものに異常がないかどうかチェックして
異常が発見されれば、それに対処するコードを書く。

同様のコードが10箇所に記述されると
本来何をしているコードなのか可読性が失われますし
同じことを何度も書くのは面倒な作業です。

例外処理を行うことで
実行時に起こりうる異常に対応するコードを
スッキリ書くことができます。

なお、発生した例外を捕捉せず放ってしまうと
実行時にエラーとなり以後の処理が中断されます。
アプリの場合はそのまま終了となります。



例外の捕捉 try 〜 catch
例外を捕捉するには、try と catchを使います。 例外を捕捉したいコードの範囲をtryで囲んで 実際に例外が発生するとcatchで捕捉します。 では、ポピュラーな例として 0で除算を行って例外が発生するコードを書いてみます。
【例外を捕捉するコード】
    try
    {
        int a = 10, b = 0;
        int c = a / b;                      //0で除算しているので実行時に例外発生
        Console.WriteLine( "処理終了" );    //ここは実行されない
    }
    catch
    {
        Console.WriteLine( "例外が捕捉されました。" );
    }
【実行結果】
    例外が捕捉されました。
【書式】
    try
    {
        例外を捕捉したいコード
    }
    catch
    {
        例外が捕捉された場合に実行するコード
    }
まず、tryで 例外を捕捉したい部分のコードを{}で囲みます。 次に、それと対にして 例外が発生し、それが捕捉された場合にどう振舞うかを 次のcatchブロックの中に記述します。 このように、 ほとんどの場合tryとcatchはペアで記述します。 上記サンプルは文法的には正しいので コンパイルエラーは起こりません。 実行時に、c = a / b(0での除算)を行ったところで例外が発生します。 発生した例外はすぐさまcatchで捕捉されるので その次の行の"処理終了"は出力されません。 そして、例外が捕捉されると catchブロックの中のコードが実行されます。 上記は極端で強引なサンプルですが、例えば ファイルをオープンして読み書きを行おうと試みたが 何かの原因で失敗した場合、 そのファイルをクローズして後始末をする、 といった例が考えられます。 データベースへのアクセスや通信の世界の話でも同様です。 例外処理は様々なケースで有効に利用できます。
【補足】
    例外を捕捉(catch)することで
    本来であればそこで処理が中断されるようなケースを救い
    その後の処理を続行できるメリットがあります。

    catchされた後は、
    catchブロックの後の処理が続行されます。

    また、次節で説明しますが
    例外には種類があります。
    本例ではcatchの後に例外の種類の指定がないので
    ここではすべての例外が捕捉されます。
例外の種類 System.Exception
例外には種類があります。 前述の項では、例外は単に例外として処理され それがどんな種類の例外であるのか また、発生した例外の種類に応じて それぞれに対応するための適切な処理が行われていません。 ここでは例外の種類について説明します。 まず、C#では例外クラスが用意されています。 色々な種類の例外がありますが すべての例外の源となるクラスは System.Exception クラス です。 そして、前述の例で扱った0で除算した際の例外は System.DivideByZeroException クラス として定義されています。 それでは、前述のすべての例外を捕捉するサンプルを 0で除算した際の例外だけを捕捉するように書き換えてみます。
【0での除算例外を捕捉するコード】
    try
    {
        int a = 10, b = 0;
        int c = a / b;
        Console.WriteLine( "処理終了" );
    }
    catch(DivideByZeroException ex)
    {
        // Messageプロパティーに例外内容文字列が格納されているので出力
        Console.WriteLine( ex.Message );
    }
【実行結果】
    0 で除算しようとしました。
【書式】
    try
    {
        例外を捕捉したいコード
    }
    catch( 例外クラス名 例外変数名 )
    {
        例外が捕捉された場合に実行するコード
    }
catchのところに 捕捉したい例外クラスを記述する点がポイントです。 どんな種類の例外が発生したのかがはっきりしたことで より適切な対処ができるようになりました。
複数の例外を捕捉
実行時に発生する例外は1種類とは限りません。 0での除算、オーバーフロー、ファイルアクセス時の異常、などなど 様々な例外が発生します。 ここでは複数の例外を捕捉する方法を示します。 具体的には catch節を捕捉したい例外の種類の数だけ連続して記述します。
【複数の例外を捕捉するコード】
    try
    {
        int a = 10, b = 0;
        int c = a / b;
        Console.WriteLine( "処理終了" );
    }
    catch(DivideByZeroException ex)
    {
        // 0での除算を捕捉
        Console.WriteLine( ex.Message );
    }
    catch(OverflowException ex)
    {
        // オーバーフローを捕捉
        Console.WriteLine( ex.Message );
    }
    catch(Exception ex)
    {
        // その他全般の例外を捕捉
        Console.WriteLine( ex.Message );
    }
【実行結果】
    0 で除算しようとしました。
この時の動きですが、 上記例ではtry節の中で0で除算を行い DivideByZeroException例外が発生します。 すると、 catch(DivideByZeroException ex) { // 0での除算を捕捉 Console.WriteLine( ex.Message ); } の部分のコードだけが実行され その他のcatch説のコードは実行されません。 このように例外の種類に応じて それぞれのケースに応じて適切な処理を記述することができます。 なお、上記サンプルでは 0での除算とオーバーフローだけが警戒されていますが 万が一、それ以外の例外が発生することを考えて コードの最後の方で その他全般の例外が捕捉されるようにしています。
例外を投げる throw
例外は明示的に投げることも可能です。 throwを使います。
【明示的に例外を投げるコード】
    try
    {
        // 中略

        throw new Exception("あぼーん");

        // 中略
    }
    catch( Exception ex )
    {
        Console.WriteLine( ex.Message );
    }
【実行結果】
    あぼーん
【書式】
    throw 例外オブジェクト;
【補足】
    throwする際は、
    パラメータとして例外クラスのオブジェクトを投げるので
    例外クラスをnewしている点に注意してください。

        throw;

    として、throw単文でも例外を投げることは可能ですが
    その場合、それを捕捉するためのcatchは

        catch
        {
        }

    或いは

        catch( Exception ex )
        {
        }

    のいずれかとなります。(どちらでも良いです)
色々な例外クラスがありますが 上記コードではExceptionクラスを用い、 さらにコンストラクタの引数として エラーメッセージを設定しています。 他にどのような例外クラスがあるのかについては MSDNなどを参照してみてください。 また、catchした例外を さらにthrowして外側のtry〜catchへ通知することも可能です。 (次節参照)
tryのネスト
try〜catchはネストして記述可能です。
【関数内で完結したtry〜catchのネストコード】
    static void Main(string[] args)
    {
        try
        {
            try
            {
                int a = 10, b = 0;
                int c = a / b;
                Console.WriteLine( "処理終了" );
            }
            catch(OverflowException ex)
            {
                // オーバーフローを捕捉
                Console.WriteLine( ex.Message );
            }
        }
        catch(Exception ex)
        {
            // 内側のcatchで捕捉できなかったその他全般の例外を捕捉
            Console.WriteLine( ex.Message );
        }
    }
【実行結果】
    0 で除算しようとしました。
極端な例ですが、上記例では、 内側のtry節の中で0で除算が行われ DivideByZeroException例外が発生します。 しかし、内側のcatch節はこの例外を処理しておらず (対処しているのはOverflowException例外のみ) 外側のcatch節で捕捉されます。 また、catchした例外を さらに外側のcatchへthrowすることも可能です。 【おまけ】 次に示すコードでは2つのクラスが登場します。 ひとつはアプリケーションの最初に実行される エントリポイント(Main関数)を持ったクラスです。 もうひとつは例外を発生させるクラスです。 発生した例外がどのようにcatchされるのか また、catchされた例外がさらにthrowされ、 それがまたどこでcatchされるのか デバッガーを使って順序を確認してみてください。 参考になると思います。
【例外が通知される様子を確認するコード】
    using System;

    namespace Sample
    {
        /// 
        /// 例外を起こすサンプルクラス
        /// 
        class Class2
        {
            public void Func()
            {
                try
                {
                    int a = 10, b = 0;
                    int c = a / b;                  // 1.最初にここで例外発生
                    Console.WriteLine( "処理終了" );
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);  // 2.ここで最初にcatchされます
                    throw;                          // 3.外側のtryへ例外をthrow
                }
            }
        }

        class Class1
        {
            /// 
            /// アプリケーションのメイン エントリ ポイントです。
            /// 
            [STAThread]
            static void Main(string[] args)
            {
                try
                {
                    // Class2のインスタンス生成
                    Class2 c2 = new Class2();

                    // Class2のFunc関数を呼び出し
                    c2.Func();
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.Message);  // 4.Class2からのthrowをcatch
                }
            }
        }
    }
【実行結果】
    0 で除算しようとしました。
    0 で除算しようとしました。
例外の後処理 finally
C#の例外処理には さらに便利なfinallyが用意されています。 その名の通り、 finallyブロックのコードは最後に実行されます。 これは、例外が発生した場合も、そうでない場合も関係無く どちらにせよ必ず実行されます。 例えば、ファイルをオープンして読み書きを行う場合 処理の過程で例外が発生してもしなくても 作法として最後にファイルは閉じなければなりません。 また、データベースとの接続や ネットワーク処理においては 最後に接続を切断します。 このように 必ず最後に実行したい処理をfinallyブロックに記述すると便利です。 具体的に例を示します。 テキストファイルをオープンし、内容を出力します。 ファイルが存在しなければ例外が発生します。 また、読み込みの途中で例外がするかもしれません。 いずれにしても、例外が発生してもしなくても 最後にファイルがクローズされるよう finallyでクローズ処理を記述しています。
【テキストファイルの内容を出力するコード】
    using System;

    namespace Sample
    {
        class Class1
        {
            /// 
            /// アプリケーションのメイン エントリ ポイントです。
            /// 
            [STAThread]
            static void Main(string[] args)
            {
                // ファイルを読み込むためのStreamReaderクラス
                System.IO.StreamReader reader = null;

                try
                {
                    // テキストファイルと関連付け。
                    // ここでテキストファイルが存在しなければ
                    // System.IO.FileNotFoundException 例外が発生します。
                    reader = new System.IO.StreamReader( "c:\\sample.txt" );

                    // テキストファイルの全内容を一気に読み込み
                    string strText = reader.ReadToEnd();

                    // 読み込んだ内容を出力
                    Console.WriteLine( strText );
                }
                catch(Exception ex)
                {
                    Console.WriteLine(ex.GetType().FullName + " 例外が発生しました。");
                    Console.WriteLine("例外内容:" + ex.Message);
                }
                finally
                {
                    // 例外が発生してもしなくてもファイルをクローズする。
                    // 但し、readerが有効であることを確認する。
                    if ( reader != null )
                        reader.Close();
                }
            }
        }
    }
【実行結果1:テキストファイルが存在しない場合】
    System.IO.FileNotFoundException 例外が発生しました。
    例外内容:ファイル "c:\sample.txt" が見つかりませんでした。
【実行結果2:テキストファイルが存在する場合】
    abc

    ※補足
        テキストファイルの内容により異なります。
        テキストファイルを用意する場合は
        テキストは短めの半角文字を記述してください。
【書式】
    try     ※tryは必ず必要
    {
    }
    catch   ※catchの記述は任意
    {
    }
    finally
    {
        // 最後に実行するコード
    }
オーバーフローへ対処 Checked, Unchecked
C#では、演算のオーバーフロー(桁溢れ)に対して 例外を発生させるか否かを指定することができます。 それには、checked と unchecked を使います。 まず、わざとオーバーフローが発生するコードを書いてみます。 byte型は符号なしの8バイト。 値の許容範囲は0〜255です。 ここに、128+128の結果256を代入してオーバーフローさせます。 結果は切捨てられ、0が出力されます。
【Sampleコード】
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace over
    {
        class Program
        {
            static void Main(string[] args)
            {
                byte bret, b1 = 128, b2 = 128;

                try
                {
                    bret = (byte)(b1 + b2);
                    Console.WriteLine("{0}", bret);
                }
                catch (OverflowException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
【実行結果】
    0
    続行するには何かキーを押してください . . .
オーバーフローの例外を補足するためにcatch節を記述しましたが catchされませんでした。 サンプルコードは簡単なものでしたが これが複雑なプログラムであったなら、バグの原因となったり 意図しない結果が待っています。 これを、明示的にオーバーフローが発生した場合は 例外が発生するようにcheckedを使って書き換えてみます。 具体的には、演算式部分をchecked()で囲みます。 すると、オーバーフローの例外が発生します。
【例外を発生させるコード】
    using System;
    using System.Collections.Generic;
    using System.Text;

    namespace over
    {
        class Program
        {
            static void Main(string[] args)
            {
                byte bret, b1 = 128, b2 = 128;

                try
                {
                    checked(bret = (byte)(b1 + b2)); //ここに注目
                    Console.WriteLine("{0}", bret);
                }
                catch (OverflowException ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }
    }
【実行結果】
    算術演算の結果オーバーフローが発生しました。
    続行するには何かキーを押してください . . .
【checked書式1】
    checked( 式 )
また、checkedでオーバーフローの例外を発生させたい部分を 範囲指定することも可能です。
【checked書式2】
    checked
    {
        中略
    }
逆に、オーバーフローの例外を発生させたくない場合は uncheckedを指定します。 書式はcheckedと同様です。 例外は発生しなくなりますが、 計算結果がオーバーフローした場合は 結果は切り捨てられることに注意してください。
【unchecked書式1】
    unchecked( 式 )
【unchecked書式2】
    unchecked
    {
        中略
    }




Copyright © 2008.07 - shougo suzaki