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

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


# とりあえず書いてみる
例として、今回は以下の様な動作を行う。
> フォームロード時に`C:\temp.txt`を読み込み、内容をメッセージボックスで表示する

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

```csharp: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. `try`か`catch`の処理が全て終わると`finally`の処理を行う

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

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

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

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

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

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

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