finallyブロックが例外をスローするとどうなりますか?


266

finallyブロックが例外をスローした場合、正確にはどうなりますか?

具体的には、finallyブロックの途中で例外がスローされるとどうなりますか。このブロックの残りのステートメント(後)は呼び出されますか?

例外が上方に伝播することを認識しています。


8
試してみませんか?しかし、この種のものに関して、私が最も好きなのは、finallyの前に戻り、finallyブロックから別の何かを返すことです。:)
ANeves

8
finallyブロック内のすべてのステートメントを実行する必要があります。返品はできません。msdn.microsoft.com/en-us/library/0hbbzekw(VS.80).aspx
Tim Scarborough

回答:


419

finallyブロックが例外をスローした場合、正確にはどうなりますか?

その例外は外に伝播し、より高いレベルで処理されます(できる)。

最後のブロックは、例外がスローされるポイントを超えて完了しません

以前の例外の処理中にfinallyブロックが実行されていた場合、その最初の例外は失われます。

C#4言語仕様§8.9.5:finallyブロックが別の例外をスローすると、現在の例外の処理が終了します。


9
でない限りThreadAbortException、最後のブロック全体が最初に終了します。これはクリティカルセクションであるためです。
Dmytro Shevchenko

1
@Shedal-あなたは正しいですが、それは「特定の非同期例外」、つまりThreadAbortExceptionにのみ適用されます。通常の1スレッドコードの場合、私の答えは保持されます。
Henk Holterman、2014

「最初の例外が失われました」-実際には非常に期待外れですが、Dispose()で例外をスローするIDisposableオブジェクトを見つけたため、「using」句内で例外が失われています。
Alex Burtsev 2014

「Dispose()で例外をスローするIDisposableオブジェクトを見つけました」 -控えめに言っても奇妙です。MSDNで読む:例外を除いてDispose(bool)内から例外をスローする...
Henk Holterman

1
@HenkHolterman:直接接続されたプライマリハードディスクでは、ディスクフルエラーはあまり一般的ではありませんが、プログラムはリムーバブルディスクまたはネットワークディスクにファイルを書き込むことがあります。問題はそれらとはるかに共通することができます。ファイルが完全に書き込まれる前に誰かがUSBスティックを引っ張ってしまった場合は、目的の場所に到着してファイルが破損しているのを見つけるまで待つよりも、すぐに伝えるほうがよいでしょう。あるエラーが発生したときに以前のエラーを引き継ぐの賢明な動作ですが、以前のエラーが発生していない場合は、報告せずに問題を報告する方が適切です。
スーパーキャット2015年

101

このような質問では、通常、空のコンソールアプリケーションプロジェクトをVisual Studioで開いて、小さなサンプルプログラムを記述します。

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

プログラムを実行するcatchと、finallyブロックが実行される正確な順序が表示されます。例外がスローされた後のfinallyブロック内のコードは実行されないことに注意してください(実際には、このサンプルプログラムでは、Visual Studioは到達不能コードを検出したことを警告します):

tryブロックからスローされた例外を処理する内部catchブロック。
インナー最終ブロック
finallyブロックからスローされた例外を処理する外部キャッチブロック。
ついに外側をブロック

追加備考

Michael Damatovが指摘したtryように、(内部の)catchブロックで処理しないと、ブロックからの例外は「食べられます」。実際、上記の例では、再スローされた例外は外側のcatchブロックには表示されません。それをさらに明確にするために、次のわずかに変更されたサンプルを見てください。

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

出力からわかるように、内部例外は「失われています」(つまり無視されています)。

インナー最終ブロック
finallyブロックからスローされた例外を処理する外部キャッチブロック。
ついに外側をブロック

2
内側のキャッチで例外を
スローするため

4
@Theofanis Pantelides:いいえ、finallyブロックは(ほぼ)常に実行されます。これは、この場合も内部のfinal ブロックに当てはまります(サンプルプログラムを自分で試してみてください(回復不能の場合は、finallyブロックは実行されません)例外、たとえばEngineExecutionException、ただし、そのような場合、プログラムはとにかくすぐに終了します)
Dirk Vollmar

1
ただし、最初のコードの最初のキャッチでのスローの役割はわかりません。コンソールアプリケーションを使用して、または使用せずに試しましたが、違いは見つかりませんでした。
JohnPan 2016年

@johnpan:ポイントは、tryブロックとcatchブロックの両方が例外をスローした場合でも、finallyブロックが常に実行されることを示すことでした。実際、コンソール出力に違いはありません。
Dirk Vollmar 2016年

10

保留中の例外がある場合(tryブロックにはあるfinallyががない場合catch)、新しい例外がその例外を置き換えます。

保留中の例外がない場合、finallyブロックの外側で例外をスローするのと同じように機能します。


例外を(再)スローする一致するブロックある場合も、例外が保留されている可能性がありますcatch
stakx-2015年


3

元の例外がより重要な場合に備えて、「元の例外」(tryブロックでスロー)を保存し、「最終的に例外」(finallyブロックでスロー)を犠牲にする迅速な(そして明白な)スニペット:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

上記のコードが実行されると、「元の例外」がコールスタックまで伝播し、「最終的に例外」が失われます。


2

例外のために開かれなかったストリームを閉じようとするエラーをキャッチするために、これを行わなければなりませんでした。

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

webRequestが作成されたが、接続エラーが発生した場合

using (var sw = webRequest.GetRequestStream())

次に、最後に、webRequestが作成されたために開いていたと見なした接続をクローズしようとする例外をキャッチします。

最終的に内部にtry-catchがなかった場合、このコードはwebRequestをクリーンアップするときに未処理の例外を発生させます。

if (webRequest.GetRequestStream() != null) 

そこから、発生したエラーを適切に処理せずにコードが終了し、呼び出しメソッドに問題が発生します。

これが例として役立つことを願っています


1

別の例外がアクティブなときに例外をスローすると、最初の例外が2番目の(後で)例外に置き換えられます。

何が起こるかを示すコードを次に示します。

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • コードを実行すると、「2番目の例外」が表示されます
  • tryおよびcatchステートメントのコメントを外すと、「最初の例外」が表示されます
  • また、スローのコメントを外します。ステートメントと「2番目の例外」が再び表示されます。

特定のコードブロックの外側でのみキャッチされる「重大な」例外のクリーンアップが、その中でキャッチされて処理される例外をスローする可能性があることは注目に値します。例外フィルター(vb.netで使用可能ですが、C#では使用できません)を使用すると、この状態を検出できます。コードがコードを「処理」するためにできることはそれほど多くありませんが、何らかのロギングフレームワークを使用している場合は、ほぼ確実にロギングする価値があります。クリーンアップ内で発生する例外がシステムのメルトダウンをトリガーするというC ++のアプローチは醜いですが、例外がなくなるのは見た目が悪いです。
スーパーキャット2012年

1

数ヶ月前、私もこのようなことに直面しました、

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

そのような問題を解決するために、私はのようなユーティリティクラスを作りました

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

そしてこのように使われました

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

パラメータと戻り値の型を使用したい場合は別の話です


1
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

CodeAとCodeBによってスローされた例外の処理方法は同じです。

でスローされた例外finallyブロックは、コードBで例外スローとして扱い、何も特別なを持っています


詳しく説明してもらえますか?同じことを除いてどういう意味ですか?
Dirk Vollmar、

1

例外は上に伝播し、より高いレベルで処理する必要があります。例外が上位レベルで処理されない場合、アプリケーションがクラッシュします。「最終的に」ブロックの実行は、例外がスローされた時点で停止します。

例外があるかどうかに関係なく、「最終的に」ブロックの実行が保証されます。

  1. tryブロックで例外が発生した後で「finally」ブロックが実行されている場合、

  2. その例外が処理されない場合

  3. そして、finallyブロックが例外をスローした場合

その後、tryブロックで発生した元の例外は失われます。

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

詳細については素晴らしい記事


-1

例外をスローします;)他のキャッチ句でその例外をキャッチできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.