備忘録というよりはまあ、他人に教える時に「ここに書いてるから!」って言って楽したいので。
とりあえず書いてみる
例として、今回は以下の様な動作を行う。
フォームロード時に
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をメソッド側に書いておくことで、エラーメッセージを使い分けたりもできる。
ドヤ顔で語って来ましたが、まあこれも教えていただいた内容なんですけどね。
確かにこの方法なら、エラーメッセージ表示中にファイルの編集ができない!みたいなことも回避できます。すごい。
テキストに関わらず、ファイルを使った読み書き、またデータベースとのやり取りでも上記の方法でできる。
ぜひお試しあれ。