【C#】try-finally文でreturn時にも確実に後処理を実行する方法

メソッドの実行中に例外が発生した場合だけでなく、正常に終了した場合や、途中でreturn文によってメソッドを抜ける場合であっても、必ず実行したい処理(リソースの解放、ログの出力、状態の復元など)が存在します。

C#のtry-finally構文を使用することで、どのような経路でブロックを抜けたとしても、確実に後処理を実行することが保証されます。ここでは、処理の開始と終了を記録するロガーの実装を例に、returnが含まれる場合のfinallyブロックの挙動について解説します。


目次

try-finallyの基本挙動

finallyブロックに記述されたコードは、tryブロック内の処理がどのように終了したかにかかわらず実行されます。

特筆すべき点は、tryブロック内でreturn文が実行された場合でも、メソッドを抜ける直前に必ずfinallyブロックが実行されるという仕様です。

実践的なコード例:処理状態の確実なリセット

以下のコードは、データの処理状況を管理するメソッドの例です。条件によって早期リターン(return)する場合でも、finallyブロックを使用して「処理終了」のステータス更新を確実に行っています。

using System;

namespace ProcessManagement
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("--- アプリケーション開始 ---");
            
            // ケース1: 正常に最後まで処理が進む場合
            int result1 = ProcessData(10);
            Console.WriteLine($"結果1: {result1}");

            Console.WriteLine();

            // ケース2: 途中でreturnする場合
            int result2 = ProcessData(-5);
            Console.WriteLine($"結果2: {result2}");

            Console.WriteLine("--- アプリケーション終了 ---");
        }

        static int ProcessData(int input)
        {
            Console.WriteLine($"[開始] データ処理を開始します (Input: {input})");
            
            try
            {
                // バリデーションチェック
                if (input < 0)
                {
                    Console.WriteLine("  -> 入力値が不正です。処理を中断します。");
                    // ここで return しますが、メソッドを抜ける前に finally が呼ばれます
                    return 0;
                }

                // メインの処理
                Console.WriteLine("  -> データを加工しています...");
                int calculatedValue = input * 2;
                
                // 正常終了時の return
                return calculatedValue;
            }
            // catchブロックは必須ではありません。例外ハンドリングが不要なら省略可能です。
            finally
            {
                // returnする直前、あるいは例外発生時に必ず実行される
                Console.WriteLine($"[終了] リソースのクリーンアップ完了 (Input: {input})");
            }
        }
    }
}

実行結果

--- アプリケーション開始 ---
[開始] データ処理を開始します (Input: 10)
  -> データを加工しています...
[終了] リソースのクリーンアップ完了 (Input: 10)
結果1: 20

[開始] データ処理を開始します (Input: -5)
  -> 入力値が不正です。処理を中断します。
[終了] リソースのクリーンアップ完了 (Input: -5)
結果2: 0
--- アプリケーション終了 ---

技術的なポイントと注意点

1. 実行順序の仕組み

tryブロック内でreturn文に到達した場合、以下の順序で処理が進みます。

  1. returnの戻り値が評価(計算)され、一時的に保持される。
  2. 制御がfinallyブロックに移る。
  3. finallyブロック内のコードが実行される。
  4. 保持されていた戻り値を持って、メソッドから実際に抜ける。

このため、finallyブロック内で変数の値を書き換えても、すでに評価済みの戻り値(値型の場合)には影響を与えません。

2. finallyが実行されない唯一の例外

「必ず実行される」と言われるfinallyですが、極めて特殊な状況下では実行されないことがあります。

  • Environment.FailFastメソッドが呼び出された場合。
  • プロセスが強制終了された場合(タスクマネージャーでの終了や停電など)。
  • StackOverflowExceptionなどの回復不能な例外が発生した場合(状況による)。

通常のアプリケーション開発においては「実行される」という前提で設計して問題ありません。

3. usingステートメントとの関係

ファイル操作やデータベース接続などのリソース解放(Disposeメソッドの呼び出し)については、try-finallyを明示的に書く代わりに**usingステートメント**を使用することが推奨されます。 C#コンパイラは、using文をコンパイル時に自動的にtry-finallyブロックへ展開し、finally内でDisposeを呼び出すコードを生成します。

まとめ

try-finally構文は、メソッドがどのような経路で終了しようとも、整合性を保つための終了処理(後処理)を記述する場所として最適です。特にreturnbreakcontinueによるジャンプが発生しても追随して実行される特性を理解しておくことで、バグの少ない堅牢なコードを実装できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次