備忘録というよりはまあ、他人に教える時に「ここに書いてるから!」って言って楽したいので。
とりあえず書いてみる
例として、今回は以下の様な動作を行う。
フォームロード時に
C:\temp.txt
を読み込み、内容をメッセージボックスで表示する
ごくごく単純。
で、ファイルを読んだらそれの後処理も必ず行うとする。
まっすぐにコードを書くと、こんな感じ。
private void Form1_Load(object sender, EventArgs e)
{
string filePath = @"C:\temp.txt";
System.IO.StreamReader sr = new StreamReader(filePath, Encoding.Default);
MessageBox.Show(sr.ReadToEnd());
sr.Close();
}
まあ間違ってはないんだけど。
これだと、**「読み込みに失敗した時にsr
がクローズされない」**という問題が発生する。
で、これを回避するためにtry-catch-finally
を使う。
try-catch-finallyを使う
try-catch-finally
を使うと、
-
try
の処理中に - 予期せぬ事態(例外)が発生したら
-
try
の処理を中断してcatch
の処理を行う -
try
かcatch
の処理が全て終わるとfinally
の処理を行う
て感じにできる。
今回の場合だと、読込中に例外が発生したか否かに関わらずsr.Close();
を行いたいから、
private void Form1_Load(object sender, EventArgs e)
{
string filePath = @"C:\temp.txt";
System.IO.StreamReader sr = null;
try
{
sr = new System.IO.StreamReader(filePath, Encoding.Default);
MessageBox.Show(sr.ReadToEnd());
}
catch (Exception)
{
MessageBox.Show("Error!!");
}
finally
{
if (sr != null)
{
sr.Close();
}
}
}
try
の前にsr
を宣言してるのは、try
内で宣言されたものはcatch
やfinally
では参照できないから。
スコープの問題、というやつですね。
あとsr = null
のままfinally
の処理を行う(null
のものをClose()する
)のはよろしくないので、
クローズ処理の前に判定を置いてる。
で、これでOK…**かと思いきや、そうじゃない。確かに後処理はしてるんだけど、
これだと「メッセージボックスが表示されてる間ファイルがロックされる」**という事態に。
クローズ処理は先に行いたい
一度読み込んだらもうファイルに用はないので、すぐにクローズしてしまいたい。
じゃあどうすんの、てことですが。
try-catch
の中にtry-finally
を入れるてことをします。
これはまず見たほうが早そう。
private void Form1_Load(object sender, EventArgs e)
{
string filePath = @"C:\temp.txt";
try
{
System.IO.StreamReader sr = null;
string context = string.Empty;
try
{
sr = new System.IO.StreamReader(filePath, Encoding.Default);
context = sr.ReadToEnd();
}
finally
{
if (sr != null)
{
sr.Close();
}
}
MessageBox.Show(context);
}
catch (Exception)
{
MessageBox.Show("Error!!");
}
}
おわかりいただけるだろうか。
最初のtry
の中で例外が発生したら、catch
に飛ばされる。
この段階ではsr
はnull
なので、クローズ処理は必要ない。
続いて次のtry
に入る。この中で例外が発生したら、まず最初にfinally
に飛ばされる。
ここでクローズ処理。その後catch
に飛ばされる。
不思議に思われるかもしれないが、2つめのtry
内の処理は結局1つめのtry
内に存在しているので、
2つめのtry
で発生した例外はすなわち1つめのtry
で発生した例外とも言えるのだ。
よってここで先にクローズ処理が行われた後、エラーメッセージが表示される。
無事データを読み終わったらsr
のデータを変数に退避させ、クローズ処理。
そしてメッセージボックスを表示。ここで例外が発生しても、
既にsr
のクローズ処理は行っているので、ファイルがロックされているなどということはない。
完璧である。
分割しよう
若干処理が長くなるのが欠点ではあるが、確実にクローズ処理を行ってからその他の処理を行うので、
面倒な事態を回避することが可能。
自分の場合、大抵は以下のようにメソッドを分けて使う。
private void Form1_Load(object sender, EventArgs e)
{
string filePath = @"C:\temp.txt";
try
{
string context = getContext(filePath);
MessageBox.Show(context);
}
catch (Exception)
{
MessageBox.Show("Error!!");
}
}
private string getContext(string filePath)
{
System.IO.StreamReader sr = null;
try
{
sr = new System.IO.StreamReader(filePath, Encoding.Default);
return sr.ReadToEnd();
}
finally
{
if (sr != null)
{
sr.Close();
}
}
}
詳しい処理順序は知らないけど、return
があってもちゃんとクローズ処理はしてくれるらしい。
優秀。
読み込みメソッドなんかは使い回すことが多々あるので、try-catch
だけイベント側に書いて、
try-finally
をメソッド側に書いておくことで、エラーメッセージを使い分けたりもできる。
ドヤ顔で語って来ましたが、まあこれも教えていただいた内容なんですけどね。
確かにこの方法なら、エラーメッセージ表示中にファイルの編集ができない!みたいなことも回避できます。すごい。
テキストに関わらず、ファイルを使った読み書き、またデータベースとのやり取りでも上記の方法でできる。
ぜひお試しあれ。