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: }
これを実行すると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文抜きでも構わない。
例外の発生場所とキャッチする場所は、同じメソッド内になくてもよい。そのメソッドを呼び出しているメソッドでもよいし、さらにそのメソッドを呼び出しているメソッドでもよい。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: }
これを実行すると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文でキャッチされる。
このように、エラー処理は発生したメソッド内で行う必要はない。同じようなエラーが発生し得る個所が多い場合は、それらの呼び出し元のメソッド内で、一括して処理することもできる。
例外はシステムのライブラリが発生させると決まったものではない。ユーザー・プログラム内に、例外を発生させるコードを書くこともできる。例えば、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: }
これを実行すると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.