「キャッチ、いつ」で例外をキャッチする


94

特定の条件が満たされたときにキャッチハンドラーを実行できるC#のこの新機能を見つけました。

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

これがいつ役立つかを理解しようとしています。

次のようなシナリオが考えられます。

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

しかし、これも同じハンドラ内で実行でき、ドライバの種類に応じて異なるメソッドに委任できるものです。これにより、コードが理解しやすくなりますか?間違いなく違います。

私が考えることができる別のシナリオは次のようなものです:

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

繰り返しますが、これは私ができることです:

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

'catch、when'機能を使用すると、ハンドラー自体がスキップされ、スタックの巻き戻しがハンドラー内の特定のユースケースの処理に比べてはるかに早く発生するため、例外処理が速くなりますか?この機能により適した特定の使用例はありますか?


8
when例外自体にアクセスする必要がある場合に役立ちます
Tim Schmelter

1
しかし、これは、ハンドラブロック自体の内部でも実行できることです。「やや整理されたコード」以外のメリットはありますか?
MS Srikkanth 2016

3
しかし、あなたはすでにあなたが望まない例外を処理しました。これのどこかでそれをキャッチしたい場合はどうなりtry..catch...catch..catch..finallyますか?
Tim Schmelter 16

4
@ user3493289:その引数に続いて、例外ハンドラーでの自動型チェックも必要ありません。許可catch (Exception ex)、型のチェックなどしかできませんthrow少し整理されたコード(コードノイズの回避)が、この機能が存在する理由です。(これは実際には多くの機能に当てはまります。)
Heinzi

2
@TimSchmelterありがとう。回答として投稿してください。したがって、実際のシナリオは「処理の条件が例外に依存する場合」となり、この機能を使用します/
MS Srikkanth

回答:


118

Catchブロックでは、例外のタイプでフィルタリングできます。

catch (SomeSpecificExceptionType e) {...}

このwhen句を使用すると、このフィルターを一般的な式に拡張できます。

したがって、ここで例外を処理する必要があるかどうかを判断するために例外タイプが十分に区別されない場合に、このwhen句を使用します。


一般的な使用例は、実際には複数の異なる種類のエラーのラッパーである例外タイプです。

これは私が実際に使用したケースです(VBではかなり長い間この機能がすでにあります)。

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

も同じですSqlExceptionが、ErrorCodeプロパティもあります。代替案は次のようなものです:

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

これは間違いなくエレガントではなく、スタックトレースわずかに壊します

さらに、同じtry-catch-blockで同じタイプの例外を2回記述することができます。

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

これはwhen条件なしでは不可能です。


2
2番目のアプローチでも、別のでそれをキャッチすることはできませんcatchか?
Tim Schmelter 16

@TimSchmelter。そうだね。すべての COMExceptionを同じブロックで処理する必要があります。
ハインツィ

when使用すると、同じ例外タイプを複数回処理できます。それは決定的な違いなので、それについても言及する必要があります。これを行わないwhenと、コンパイラエラーが発生します。
Tim Schmelter 16

1
私に関する限り、「簡単に言えば」に続く部分が答えの最初の行になるはずです。
CompuChip 2016

1
@ user3493289:醜い尻のコードでもそうです。あなたは「そもそもこの混乱に陥ってはならない、コードを再設計する」と考え、「この設計を上品にサポートする方法、言語を再設計する方法があり得る」と考えています。この場合、catch句のセットをいかに醜くするかについて、ある種のしきい値があります。そのため、特定の状況をより醜くしないようにすることで、しきい値内でより多くのことを実行できます:-)
Steve Jessop

37

Roslynのwiki(強調鉱山)から:

例外フィルターはスタックを無害にするため、キャッチして再スローするよりも望ましいです。例外によって後でスタックがダンプされる場合、最後に再スローされた場所だけでなく、スタックが最初にどこから来たかを確認できます。

また、副作用のために例外フィルターを使用することは、一般的に受け入れられている「乱用」の形式です。例:ロギング。彼らは、その進路を妨害することなく、「飛行中」の例外検査できます。これらの場合、フィルターは多くの場合、副作用を実行する偽の戻りヘルパー関数の呼び出しになります。

private static bool Log(Exception e) { /* log it */ ; return false; }

 try {  } catch (Exception e) when (Log(e)) { }

最初の点は説明する価値があります。

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

例外が発生するまでこれをWinDbgで実行し、スタックを印刷すると!clrstack -i -a、次のフレームだけが表示されますA

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

ただし、プログラムを使用するように変更した場合when

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

スタックにも含まれていることがわかります Bのフレーム。

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

この情報は、クラッシュダンプをデバッグするときに非常に役立ちます。


7
それは私を驚かせます。throw;(とは対照的にthrow ex;)スタックも無害のままにしませんか?副作用の場合は+1。私はそれを承認するかどうかはわかりませんが、その手法について知っておくとよいでしょう。
ハインツィ

13
それは間違いではありません-これはスタックトレースを参照していません-スタック自体を参照しています。デバッガー(WinDbg)でスタックを見ると、を使用した場合でもthrow;、スタックがほどかれ、パラメーター値が失われます。
Eli Arbel

1
これは、ダンプをデバッグするときに非常に役立ちます。
Eli Arbel

3
@Heinzi 別のスレッド私の答えを見てください。throw;スタックトレースが少しthrow ex;変化し、大きく変化していることがわかります。
Jeppe Stig Nielsen

1
を使用throwすると、スタックトレースが少し乱されます。throwとは対照的に、使用時の行番号は異なりwhenます。
Mike Zboray 16

7

例外がスローされると、例外処理の最初のパスは、スタックを巻き戻す前に例外がキャッチされる場所を識別します。「catch」の場所が特定されると、すべての「finally」ブロックが実行されます(例外が「finally」ブロックをエスケープすると、前の例外の処理が中止される場合があります)。それが起こると、コードは「キャッチ」で実行を再開します。

「when」の一部として評価される関数内にブレークポイントがある場合、そのブレークポイントは、スタックの巻き戻しが発生する前に実行を一時停止します。対照的に、「キャッチ」でのブレークポイントは結局のところ実行を一時停止するだけですfinallyハンドラーがます。

最後に、foocallの23行目と27行目bar、および23行目の呼び出しが例外をスローし、それがfoo57行目でキャッチされbarて再スローされた場合、スタックトレースは、57行目からの呼び出し中に例外が発生したことを示唆します[再スローの場所] 、23行目または27行目で例外が発生したかどうかに関する情報を破棄します。whenそもそも例外の捕捉を回避するために使用することで、このような妨害を回避できます。

ところで、C#とVB.NETの両方で厄介で厄介な便利なパターンは、関数呼び出しをwhen句内で使用してfinally、関数が正常に完了したかどうかを判断するために句内で使用できる変数を設定することです。発生した例外を「解決」する望みはありませんが、それに基づいてアクションを実行する必要があります。たとえば、リソースをカプセル化するオブジェクトを返すことになっているファクトリメソッド内で例外がスローされた場合、取得されたすべてのリソースを解放する必要がありますが、基になる例外は呼び出し元に浸透する必要があります。これを意味的に(構文的にではなく)処理する最もクリーンな方法は、finally例外が発生したかどうかをブロックチェックし、発生した場合は、返されなくなったオブジェクトのために取得したすべてのリソースを解放します。クリーンアップコードは、例外を引き起こしたすべての条件を解決する望みがないcatchので、実際にはそれをすべきではなく、何が起こったかを知る必要があるだけです。次のような関数を呼び出す:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

when句内では、ファクトリ関数が何かが起こったことを知ることができます。

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