連載
» 2002年12月19日 00時00分 公開

連載 改訂版 C#入門:第18章 例外とエラー処理 (3/4)

[川俣晶(http://www.autumn.org/),(株)ピーデー(http://www.piedey.co.jp/)]

18-6 finally文はbreak文やreturn文でも有効

 finally文のブロックは、例外が起きても必ず実行されるだけではない。break文やreturn文で、tryブロックから直接抜け出すようなコードを書いても、確実に実行される。List 18-9はそれを示すサンプル・ソースである。

  1: using System;
  2:
  3: namespace Sample007
  4: {
  5:   class Class1
  6:   {
  7:     [STAThread]
  8:     static void Main(string[] args)
  9:     {
 10:       int [] array = { 1,2,3,4,5,6 };
 11:       foreach( int n in array )
 12:       {
 13:         try
 14:         {
 15:           if( n == 3 ) break;
 16:           Console.WriteLine(n);
 17:         }
 18:         finally
 19:         {
 20:           Console.WriteLine( n + "の終了処理が呼び出されました。(その1)" );
 21:         }
 22:       }
 23:       foreach( int n in array )
 24:       {
 25:         try
 26:         {
 27:           if( n == 3 ) return;
 28:           Console.WriteLine(n);
 29:         }
 30:         finally
 31:         {
 32:           Console.WriteLine( n + "の終了処理が呼び出されました。(その2)" );
 33:         }
 34:       }
 35:     }
 36:   }
 37: }

List 18-9

 これを実行するとFig.18-7のようになる。

Fig.18-7

 15行目のif文により、nが3のとき、13行目から17行目までのtryブロックから直接break文で飛び出している。しかし、Fig.18-7を見て分かるとおり、nが3のときのfinallyブロックはちゃんと実行されている。このfinallyブロックの実行後にforeach文を抜け出しているのである。同様に27行目のreturn文でも、メソッドからリターンする前に、30行目以降のfinallyブロックが実行されていることが分かるだろう。

 このように、finally文は、例外とは無関係にも利用でき、確保した資源を確実に解放したいという場合にも利用できる「優れもの」である。その際には、try文とfinally文だけ記述すればよく、catch文抜きでも構わない。

18-7 深い階層からの例外

 例外の発生場所とキャッチする場所は、同じメソッド内になくてもよい。そのメソッドを呼び出しているメソッドでもよいし、さらにそのメソッドを呼び出しているメソッドでもよい。List 18-10はそれを示したサンプルである。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample008
  6: {
  7:   class Class2
  8:   {
  9:     public void fileRead( string fileName )
 10:     {
 11:       StreamReader reader = null;
 12:       try
 13:       {
 14:         reader = new StreamReader( fileName, Encoding.GetEncoding("Shift_JIS") );
 15:         Console.Write( reader.ReadToEnd() );
 16:       }
 17:       catch( FileNotFoundException e )
 18:       {
 19:         Console.WriteLine( e.FileName + "が見つかりません。" );
 20:       }
 21:       finally
 22:       {
 23:         if( reader != null )
 24:         {
 25:           reader.Close();
 26:         }
 27:       }
 28:     }
 29:   }
 30:   class Class1
 31:   {
 32:     private static void test()
 33:     {
 34:       Class2 instance = new Class2();
 35:       try
 36:       {
 37:         instance.fileRead( "存在しない\\存在しない.txt" );
 38:       }
 39:       catch( DirectoryNotFoundException e )
 40:       {
 41:         Console.WriteLine( "ディレクトリが見つかりません。" );
 42:       }
 43:     }
 44:     [STAThread]
 45:     static void Main(string[] args)
 46:     {
 47:       try
 48:       {
 49:         test();
 50:       }
 51:       catch( Exception e )
 52:       {
 53:         Console.WriteLine( e.GetType().FullName + "の例外が発生しました。" );
 54:       }
 55:     }
 56:   }
 57: }

List 18-10

 これを実行するとFig.18-8のようになる。

Fig.18-8

 List 18-10では、14行目のStreamReaderクラスのコンストラクタがDirectoryNotFoundExceptionの例外を発生させる。コンストラクタは12行目からのtryブロック内にあるものの、この例外をキャッチするcatch文はないので、ここではキャッチされない。そこで、このメソッドを呼び出したメソッドを調べる。つまり、32行目からのtestメソッドである。ここで、問題のメソッドは37行目から呼び出されているが、これは35行目からのtryブロック内にある。このブロックに付随する39行目のcatch文がまさにDirectoryNotFoundExceptionクラスの例外をキャッチするので、このcatch文が有効になり、41行目が実行される。ここでもキャッチされない例外の場合は、47行目からのtryブロックに付随する51行目のcatch文でキャッチされることになる。51行目はExceptionクラスなので、すべての例外はここでキャッチされる。しかし、階層が低い方が優先されるので、より低い階層でチェックされるFileNotFoundExceptionクラスやDirectoryNotFoundExceptionクラスは、それぞれのcatch文でキャッチされる。

 このように、エラー処理は発生したメソッド内で行う必要はない。同じようなエラーが発生し得る個所が多い場合は、それらの呼び出し元のメソッド内で、一括して処理することもできる。

18-8 ユーザー・プログラムが発生させる例外

 例外はシステムのライブラリが発生させると決まったものではない。ユーザー・プログラム内に、例外を発生させるコードを書くこともできる。例えば、List 18-11は、実際に読み出すまでファイルのオープンを遅らせる機能を書き込んだクラスである。オープンは遅らせるが、ファイルがない場合はコンストラクタを実行した段階で例外が起きてほしいとしよう。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample009
  6: {
  7:   class DelayedFileReader
  8:   {
  9:     private string _fileName;
 10:     public DelayedFileReader( string fileName )
 11:     {
 12:       _fileName = fileName;
 13:       if( !File.Exists( fileName ) )
 14:       {
 15:         throw new FileNotFoundException( "ファイルが見つかりません。", fileName );
 16:       }
 17:     }
 18:     public string ReadToEnd()
 19:     {
 20:       StreamReader reader = null;
 21:       try
 22:       {
 23:         reader = new StreamReader(_fileName,Encoding.GetEncoding("Shift_JIS"));
 24:         return reader.ReadToEnd();
 25:       }
 26:       finally
 27:       {
 28:         if( reader != null )
 29:         {
 30:           reader.Close();
 31:         }
 32:       }
 33:     }
 34:   }
 35:   class Class1
 36:   {
 37:     [STAThread]
 38:     static void Main(string[] args)
 39:     {
 40:       DelayedFileReader reader = new DelayedFileReader("存在しない.txt");
 41:       Console.Write( reader.ReadToEnd() );
 42:     }
 43:   }
 44: }

List 18-11

 これを実行するとFig.18-9のようになる。

Fig.18-9

 List 18-11の13行目のFile.Existsメソッド(フルネームはSystem.IO.File.Exists)は、ファイルが存在するかどうかを判定するメソッドである。もし、開きたいファイルがない場合は、FileNotFoundExceptionクラスの例外を発生させる。ここでポイントになるのは、15行目のthrow文だ。throw文は例外を発生させる。例外の内容は、throwのキーワードの後に記述する値になる。通常は、例外のクラスをnewによってインスタンス生成して指定する。ここでは、FileNotFoundExceptionクラスのインスタンスを生成して指定している。もちろん、どんな例外クラスでも、このように利用できる。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。