36
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【C#】try-catch-finallyを使ってみよう

Posted at

備忘録というよりはまあ、他人に教える時に「ここに書いてるから!」って言って楽したいので。

とりあえず書いてみる

例として、今回は以下の様な動作を行う。

フォームロード時にC:\temp.txtを読み込み、内容をメッセージボックスで表示する

ごくごく単純。
で、ファイルを読んだらそれの後処理も必ず行うとする。
まっすぐにコードを書くと、こんな感じ。

Form1.cs
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を使うと、

  1. tryの処理中に
  2. 予期せぬ事態(例外)が発生したら
  3. tryの処理を中断してcatchの処理を行う
  4. trycatchの処理が全て終わるとfinallyの処理を行う

て感じにできる。
今回の場合だと、読込中に例外が発生したか否かに関わらずsr.Close();を行いたいから、

Form1.cs
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内で宣言されたものはcatchfinallyでは参照できないから。
スコープの問題、というやつですね。
あとsr = nullのままfinallyの処理を行う(nullのものをClose()する)のはよろしくないので、
クローズ処理の前に判定を置いてる。
で、これでOK…**かと思いきや、そうじゃない。確かに後処理はしてるんだけど、
これだと
「メッセージボックスが表示されてる間ファイルがロックされる」**という事態に。

クローズ処理は先に行いたい

一度読み込んだらもうファイルに用はないので、すぐにクローズしてしまいたい。
じゃあどうすんの、てことですが。
try-catchの中にtry-finallyを入れるてことをします。
これはまず見たほうが早そう。

Form1.cs

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に飛ばされる。
この段階ではsrnullなので、クローズ処理は必要ない。
続いて次のtryに入る。この中で例外が発生したら、まず最初にfinallyに飛ばされる。
ここでクローズ処理。その後catchに飛ばされる。
不思議に思われるかもしれないが、2つめのtry内の処理は結局1つめのtry内に存在しているので、
2つめのtryで発生した例外はすなわち1つめのtryで発生した例外とも言えるのだ。
よってここで先にクローズ処理が行われた後、エラーメッセージが表示される。
無事データを読み終わったらsrのデータを変数に退避させ、クローズ処理。
そしてメッセージボックスを表示。ここで例外が発生しても、
既にsrのクローズ処理は行っているので、ファイルがロックされているなどということはない。
完璧である。

分割しよう

若干処理が長くなるのが欠点ではあるが、確実にクローズ処理を行ってからその他の処理を行うので、
面倒な事態を回避することが可能。
自分の場合、大抵は以下のようにメソッドを分けて使う。

Form1.cs
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をメソッド側に書いておくことで、エラーメッセージを使い分けたりもできる。

ドヤ顔で語って来ましたが、まあこれも教えていただいた内容なんですけどね。
確かにこの方法なら、エラーメッセージ表示中にファイルの編集ができない!みたいなことも回避できます。すごい。
テキストに関わらず、ファイルを使った読み書き、またデータベースとのやり取りでも上記の方法でできる。
ぜひお試しあれ。

36
47
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
47

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?