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

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

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

18-3 例外を区別して扱う

 例外には多くの種類があり、ファイルが見付からないというのは、そのうちの1種類でしかない。StreamReaderクラスのコンストラクタは、何種類かの例外を発生させる可能性がある。例えば、ディレクトリが見付からないという例外を発生させる可能性もある。List 18-4に存在しないディレクトリの指定を書き加えたら、いったい何が起こるだろうか? List 18-5を見てほしい。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample003
  6: {
  7:   class Class1
  8:   {
  9:     [STAThread]
 10:     static void Main(string[] args)
 11:     {
 12:       try
 13:       {
 14:         StreamReader reader = new StreamReader("存在しないディレクトリ\\存在しない.txt",Encoding.GetEncoding("Shift_JIS"));
 15:         Console.Write( reader.ReadToEnd() );
 16:         reader.Close();
 17:       }
 18:       catch( FileNotFoundException e )
 19:       {
 20:         Console.WriteLine("ファイル" + e.FileName + "が見つかりません。");
 21:       }
 22:     }
 23:   }
 24: }

List 18-5

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

Fig.18-3

 Fig.18-3を見てのとおり、FileNotFoundExceptionクラスをキャッチするコードは何の働きも示さず、最初のサンプル・ソースのように、システムが例外の結果をコンソールに出力してしまった。ここで起きた例外は、FileNotFoundExceptionクラスではなく、DirectoryNotFoundExceptionクラスである。いかにtry文を書こうと、catch文で指定しなかった例外に関しては、何の機能も発揮していないことが分かるだろう。

 では、どうすれば、FileNotFoundExceptionクラスも、DirectoryNotFoundExceptionクラスも、どちらも自分でエラー処理させられるのだろうか? 答えはList 18-6のとおりだ。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample004
  6: {
  7:   class Class1
  8:   {
  9:     [STAThread]
 10:     static void Main(string[] args)
 11:     {
 12:       try
 13:       {
 14:         StreamReader reader = new StreamReader("存在しないディレクトリ\\存在しない.txt",Encoding.GetEncoding("Shift_JIS"));
 15:         Console.Write( reader.ReadToEnd() );
 16:         reader.Close();
 17:       }
 18:       catch( FileNotFoundException e )
 19:       {
 20:         Console.WriteLine("ファイル" + e.FileName + "が見つかりません。");
 21:       }
 22:       catch( DirectoryNotFoundException e )
 23:       {
 24:         Console.WriteLine("ディレクトリが見つかりません。");
 25:       }
 26:     }
 27:   }
 28: }

List 18-6

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

Fig.18-4

 このように、1個のtry文に対応するcatch文は1つでなければならないということはない。キャッチしたい例外の数だけ、catch文を並べて書いてよい。

18-4 すべての例外をキャッチする方法

 何種類もある例外をすべてキャッチしようとすると、ソースに書くべきコードも増える。しかし、トラブルがあったことだけ分かればよく、トラブルの種類ごとに処理を分けなくてもよいという状況なら話を簡単にすることができる。List 18-7は、1個のcatch文であらゆる例外をキャッチさせている例である。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample005
  6: {
  7:   class Class1
  8:   {
  9:     [STAThread]
 10:     static void Main(string[] args)
 11:     {
 12:       try
 13:       {
 14:         StreamReader reader = new StreamReader("存在しないディレクトリ\\存在しない.txt",Encoding.GetEncoding("Shift_JIS"));
 15:         Console.Write( reader.ReadToEnd() );
 16:         reader.Close();
 17:       }
 18:       catch( Exception e )
 19:       {
 20:         Console.WriteLine(e.GetType().FullName + "の例外が発生しました。");
 21:       }
 22:     }
 23:   }
 24: }

List 18-7

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

Fig.18-5

 変更したのは18行目である。例外のクラス名としてExceptionを指定した。例外で使用するクラスは、すべてExceptionクラスを継承したものである。すべての例外クラスの継承元であるExceptionクラスを指定するということは、すべての例外状況にマッチするということである。つまり、実際に起きた例外がDirectoryNotFoundExceptionクラスであっても、これはExceptionクラスから派生したクラスなので、Exceptionクラスの機能をすべて含んでおり、Exceptionクラスとして扱うこともできる。そのため、Exceptionクラスをキャッチするcatch文で、DirectoryNotFoundExceptionクラスもキャッチされるのである。

 この結果、try文の中で発生するあらゆる例外は、この18行目のcatch文でキャッチされるのである。

18-5 確実な終了処理を行うfinally

 List 18-7には大きな弱点がある。もし、StreamReaderクラスのコンストラクタで例外が起きず、ReadToEndメソッド内で例外が発生した場合、Closeメソッドが実行されずにファイルが開いたままになることだ。このサンプル・ソースはすぐにプログラム自身が終了してしまうのでたいした実害はないが、これが大きなプログラムの一部だとすると、使っていないファイルが開いたままになり、何か悪影響を及ぼす可能性がある。実際、ReadToEndメソッドはIOExceptionを発生させる可能性がある。フロッピーディスクから読み出し中にメディアを抜いてしまうような状況では、これが起こる可能性がある。

 このような状況に対処するには、catch文の中でもファイルを閉じるように書くのも1つの手段だが、もっとエレガントな方法が用意されている。それは、try文とペアで使うfinally文である。List 18-8はそれを活用して、いかなる場合にも確実にファイルを閉じるように改良したものである。

  1: using System;
  2: using System.IO;
  3: using System.Text;
  4:
  5: namespace Sample006
  6: {
  7:   class Class1
  8:   {
  9:     [STAThread]
 10:     static void Main(string[] args)
 11:     {
 12:       StreamReader reader = null;
 13:       try
 14:       {
 15:         reader = new StreamReader("存在しない.txt",Encoding.GetEncoding("Shift_JIS"));
 16:         Console.Write( reader.ReadToEnd() );
 17:       }
 18:       catch( Exception e )
 19:       {
 20:         Console.WriteLine(e.GetType().FullName + "の例外が発生しました。");
 21:       }
 22:       finally
 23:       {
 24:         Console.WriteLine("終了処理を実行します。");
 25:         if( reader != null )
 26:         {
 27:           reader.Close();
 28:         }
 29:       }
 30:     }
 31:   }
 32: }

List 18-8

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

Fig.18-6

 List 18-8で注目すべきは、try文にcatch文が続くだけでなく、さらにその後にfinally文が続いていることである。try‐catch‐finallyと続く流れは1つの定番である。finallyに続くブロックに記述されたコードは、tryブロック内の処理が正常に終わっても、何かの例外を発生させても、それらの条件に関係なく必ず実行される。つまり、ファイルを閉じるためのCloseメソッドをここに記述すれば(27行目)、例外が発生しようとしまいと、必ずファイルを閉じることができるのである。ただし、StreamReaderクラスのコンストラクタで例外が起きた場合は、ファイルが開かれる前の状態のままなのでCloseメソッドを呼ぶ意味がない。そのため、25行目のif文があり、ファイルを開いた後か、開く前かを判断している。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

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

メールマガジン登録

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