CodeGym /コース /C# SELF /finallyブロックとthrow演算子

finallyブロックとthrow演算子

C# SELF
レベル 13 , レッスン 2
使用可能

1. finallyブロックとの出会い

リソースのクリーンアップと解放

想像してみて:君が化学実験室で実験してて、全部終わった後(もしくはフラスコを何個か爆発させちゃった後でも)、必ず作業台を片付けて、薬品を流して、電気を消さなきゃいけないよね。finallyブロックはまさにそのためにあるんだ。trycatchの後、例外があってもなくても、必ず実行される部分なんだよ。


try
{
    // ここに「危険な」コードを書く。例外が発生するかも
}
catch
{
    // ここで例外をキャッチして処理する
}
finally
{
    // このコードは必ず実行される。例外があってもなくても
}
C#のtry-catch-finallyの構造

なんで必要なの? 一番の理由は、リソースを確実に解放するため。ファイルを閉じたり、データベース接続を切ったり、サーバールームのドアのロックを解除したり…。finallyがなかったら、エラーの後にリソースが「ぶら下がったまま」になることもあって、それは本当に困る。例えば、ファイルがロックされたままで、管理者でも開けなくなっちゃうとかね。

全部catchに書いちゃダメなの?

リソースの解放をcatchに書けばいいんじゃない?理論的にはできる。でも、何も問題がなかった場合、catchは実行されない。リソース解放を「絶対に」やりたいなら、finallyが必要なんだ。

現実の世界でも、成功でも失敗でも、片付けは必須な作業ってあるよね。finallyはまさにそのために生まれたんだ。

2. finallyブロックの特徴

finallyが実行されない時ってある?

これはトリックな質問!finally必ず実行される。tryブロックでreturn(メソッドから早く抜けるやつ)を使っても、新しい例外を投げても、finally絶対に実行されるんだ。


static void Test()
{
    try
    {
        Console.WriteLine("リターン前");
        return;
    }
    finally
    {
        Console.WriteLine("finallyはやっぱり実行されるよ!");
    }
}

//Test()の呼び出し
Test();
出力はこうなる:

// リターン前
// finallyはやっぱり実行されるよ!
    

でも、もしアプリが「強制終了」されたら(例えばPCの電源が落ちた、プロセスがkillされた、CLR自体が死んだとか)、finallyは実行されない。これはもうどうしようもないね。

finally演算子とコールスタック

コールスタックについては次のレクチャーで詳しくやるけど、今はざっくり説明すると、呼び出されたメソッドの積み重ねみたいなもので、プログラムがcatchを見つけるまで「降りていく」感じだよ。

もう一つ大事なポイント:tryで例外が発生して、catchでキャッチされなかった場合(例えば、適切なハンドラがない時)、プログラムは今のメソッドを抜けて、コールスタックを上に進んでいく。その時、各レベルで必ずfinallyブロックが実行されるんだ。

これで、どんな変なエラーが起きてもリソースがちゃんと解放されるって保証されるんだ。

3. throw演算子:自分で例外を投げる方法

throwって何?なんで必要?

例外をキャッチするだけじゃ足りない時もあるよね。自分でエラーを作って「外に投げたい」時、throw演算子を使うんだ。


throw new Exception("これは自分の特別なエラーだよ!");
独自の例外を作って投げる

throwはCLRに「ここでヤバいことが起きたから、自分のExceptionを投げるよ。あとは呼び出し元でなんとかして!」って伝える感じ。

新しい例外を作らずにthrowする

throw;catchブロックの中で使えて、今キャッチした例外をもう一回投げ直す時に使う。例えば、エラーの一部だけ処理して、あとは上のコードに任せたい時とかね。


try
{
    DangerousOperation();
}
catch (Exception ex)
{
    LogError(ex);
    throw; // 今の例外をもう一度投げる。コールスタックはそのまま
}
コールスタックを保ったまま例外を再スローする

もしthrow ex;って書いたら、コールスタックの情報が消えちゃう。これは良くない書き方だよ。

4. finallythrowの組み合わせは?

例外を投げてもfinallyは実行される

じゃあ、tryの中でthrowが発生して、finallyもある場合どうなるか見てみよう:


try
{
    Console.WriteLine("エラー前…");
    throw new Exception("try中にエラー発生!");
}
catch
{
    Console.WriteLine("Catchがエラーをキャッチした。");
}
finally
{
    Console.WriteLine("Finallyが実行された。");
}
例外が投げられてもfinallyは実行される

結果はこうなる:


エラー前…
Catchがエラーをキャッチした。
Finallyが実行された。

もし適切なcatchがなくても、finallyはプログラムが落ちる前に必ず実行されるよ。

細かい話:finallyの中でもthrowしたらどうなる?

finallyの中でthrowが発生した場合、その例外が前の例外を上書きしちゃう。つまり、try/catchで何が起きたかの情報は消えちゃう。だから、finallyの中でthrowするのは、すでに例外が発生してる場合はおすすめしないよ。


try
{
    throw new Exception("tryでエラー");
}
finally
{
    throw new Exception("finallyでエラー");
}
// 結果:「finallyでエラー」だけが外に出る
finallyの例外が前の例外を上書きする

5. 実践的なアドバイスとよくあるミス

初心者がよくやるミス

  • リソース解放の時にfinallyブロックを使わず、catchだけに頼っちゃう。
  • 新しい例外を発生させるかもしれないコードをfinally中に書いちゃう。これで予想外のエラーが起きる。
  • returntryの中にあっても、finallyは「スキップされない」ってことを忘れがち。必ず実行されるよ。

finallyの代わり:どっちを使う?

using構文(これは後で詳しくやるよ)が登場してから、リソース解放はもっと楽になった。でも実は、usingの裏側でもfinallyが使われてるんだ。ちょっと特殊なケース(例えばロック解除やエラー通知の送信とか)では、やっぱりfinallyを使うことになるよ。

面接でfinallyがよく出る理由

一度でも高負荷なサーバーを書いたことがあるリクルーターなら、finallyについて絶対に質問してくる。「tryの中にreturnがあって、finallythrowしたらどうなる?」とか「例外発生時にリソース解放は保証される?」とかね。これで君は答えだけじゃなくて、ちゃんと仕組みも説明できるようになった。finallyがどう動くかだけじゃなくて、なんで必要なのか、いつ使うべきか、そして本気のコードには絶対欠かせない理由まで語れるよ!

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION