19

CLI標準(パーティションIIA、第19章)およびSystem.Reflection.ExceptionHandlingClauseOptions列挙型のMSDNリファレンスページによると、 4つの異なる種類の例外ハンドラブロックがあります。

  • catch句:「指定されたタイプのすべてのオブジェクトをキャッチします。」
  • filter句:「フィルターが成功した場合にのみハンドラーを入力してください。」
  • finally節:「すべての例外と通常の終了を処理します。」
  • 障害句:「すべての例外を処理しますが、通常の終了は処理しません。」

これらの簡単な説明(CLI標準から引用)を考えると、これらは次のようにC#にマップする必要があります。

  • キャッチcatch (FooException) { … }
  • フィルタ— C#では使用できません(ただし、VB.NETではCatch FooException When booleanExpression
  • 最後にfinally { … }
  • 障害catch { … }

実験:

簡単な実験では、このマッピングは.NETのC#コンパイラが実際に行うことではないことが示されています。

// using System.Linq;
// using System.Reflection;

static bool IsCatchWithoutTypeSpecificationEmittedAsFaultClause()
{
    try
    {
        return MethodBase
               .GetCurrentMethod()
               .GetMethodBody()
               .ExceptionHandlingClauses
               .Any(clause => clause.Flags == ExceptionHandlingClauseOptions.Fault);
    }
    catch // <-- this is what the above code is inspecting
    {
        throw;
    }
}

このメソッドはを返しますfalse。つまりcatch { … }、障害句として発行されていません。

clause.Flags == ExceptionHandlingClauseOptions.Clause同様の実験では、例外タイプが指定されていなくても、実際にはcatch句が発行された( )ことが示されています。

質問:

  1. 本当にcatch節である場合catch { … }、fault節はcatch節とどのように異なりますか?
  2. C#コンパイラはfault句を出力しますか?
4

5 に答える 5

15

4種類の例外ハンドラブロックがあります。

  • catch句:「指定されたタイプのすべてのオブジェクトをキャッチします。」
  • filter句:「フィルターが成功した場合にのみハンドラーを入力してください。」
  • finally節:「すべての例外と通常の終了を処理します。」
  • 障害句:「すべての例外を処理しますが、通常の終了は処理しません。」

これらの簡単な説明(CLI標準から引用)を考えると、これらは次のようにC#にマップする必要があります。

  • キャッチcatch (FooException) { … }
  • フィルタ— C#1では使用できません(ただし、VB.NETではCatch FooException When booleanExpression
  • 最後にfinally { … }
  • 障害catch { … }

それはあなたが間違った最後の行です。説明をもう一度読んでください。faultfinally実質的に同じように説明されています。それらの違いは、finally常に入力されるのに対し、制御が例外を介してビアをfault離れる場合にのみ入力されることです。tryこれは、catchブロックがすでに動作している可能性があることを意味することに注意してください。

これをC#で書く場合:

try {
    ...
} catch (SpecificException ex) {
    ...
} catch {
    ...
}

tryその場合、制御がビアを離れた場合に3番目のブロックに入る方法はありませんSpecificException。そのためcatch {}、はのマッピングではありませんfault


1人々はコメントでこれに言及し続けているので、a)回答のこの部分は元の質問からの引用であり、b)はい、when句はその後C#に追加されました。しかし、それは質問の時点では正確であり、この回答が回答に焦点を当てているものではありません。

于 2012-08-16T13:36:43.083 に答える
14

.NET例外は、オペレーティングシステムの例外サポートに便乗します。Windowsでは構造化例外処理と呼ばれます。Unixオペレーティングシステムにも似たようなシグナルがあります。

管理された例外は、SEH例外の非常に特殊なケースです。例外コードは0xe0434f53です。最後の3つの16進ペアは「COM」と綴り、.NETの開始方法について説明しています。

プログラムは一般に、管理された例外だけでなく、例外がいつ発生して処理されるかを知ることに関係している可能性があります。これは、MSVCC++コンパイラでも確認できます。catch(...)句は、C++例外のみをキャッチします。ただし、/ EHaオプションを使用してコンパイルすると、例外が発生します。本当に厄介なものを含めて、アクセス違反のようなプロセッサ例外。

障害句はCLRのバージョンであり、関連するブロックは、管理対象の例外だけでなく、すべてのオペレーティングシステムの例外に対して実行されます。C#およびVB.NET言語はこれをサポートしておらず、管理された例外の例外処理のみをサポートしています。しかし、他の言語は、C ++/CLIコンパイラがそれらを発行していることしか知りません。たとえば、「スタックセマンティクス」と呼ばれるそのバージョンのusingステートメントで実行されます。

C ++ / CLIがそれをサポートすることは理にかなっています。結局のところ、マネージコードからネイティブコードを直接呼び出すことを強力にサポートする言語です。ただし、C#とVB.NETの場合は、ピンボークマーシャラーまたはCLRのCOM相互運用レイヤーを介してアンマネージコードを実行するだけです。これは、管理されていない例外を管理された例外に変換する「catch-them-all」ハンドラーをすでに設定しています。これは、System.AccessViolationExceptionを取得するメカニズムです。

于 2012-08-16T13:52:34.727 に答える
10

1.本当にcatch句の場合catch { … }、fault句はcatch句とどのように異なりますか?

C#コンパイラ(少なくとも.NETに付属しているコンパイラ)は、実際には実際に catch { … }そうであるかのようにコンパイルされているように見えますcatch (object) { … }。これは、以下のコードで表示できます。

// using System;
// using System.Linq;
// using System.Reflection;

static Type GetCaughtTypeOfCatchClauseWithoutTypeSpecification()
{
    try
    {
        return MethodBase
               .GetCurrentMethod()
               .GetMethodBody()
               .ExceptionHandlingClauses
               .Where(clause => clause.Flags == ExceptionHandlingClauseOptions.Clause)
               .Select(clause => clause.CatchType)
               .Single();
    }
    catch // <-- this is what the above code is inspecting
    {
        throw;
    }
}

そのメソッドはを返しますtypeof(object)

したがって、概念的には、フォールトハンドラー は catch { … };に似ています。ただし、C#コンパイラは、その正確な構成のコードを生成することはありませんが、catch (object) { … }概念的にはcatch句であると偽ります。したがって、catch句が発行されます。

補足: JeffreyRichterの著書「CLRviaC#」には、いくつかの関連情報があります(pp。472–474):つまり、CLRでは、Exceptionオブジェクトだけでなく、任意の値をスローできます。ただし、CLRバージョン2以降、非値Exceptionは自動的にRuntimeWrappedExceptionオブジェクトにラップされます。したがって、C#がの代わりにに変換catchされるのは少し意外なようです。ただし、これには理由があります。属性を適用することで、 CLRに非値をラップしないように指示できます。catch (object)catch (Exception)Exception[assembly: RuntimeCompatibility(WrapNonExceptionThrows = false)]

ちなみに、VB.NETコンパイラは、C#コンパイラとは異なり、に変換CatchされCatch anonymousVariable As Exceptionます。


2. C#コンパイラはfault句を出力しますか?

明らかに、の障害句を出力しませんcatch { … }。ただし、Bart de Smetのブログ投稿「リーダーチャレンジ– C#のフォールトハンドラー」は、C#コンパイラ特定の状況でフォールト句を生成することを示唆しています。

于 2012-08-16T13:13:32.760 に答える
8

人々が指摘しているように、一般的に言えば、C#コンパイラはフォールトハンドラーを生成しません。ただし、stakxは、C#コンパイラにフォールトハンドラーを生成させる方法に関するBartdeSmetのブログ投稿にリンクしています。

C#は、フォールトハンドラーを使用して、イテレーターブロック内にあるusingステートメントを実装します。たとえば、次のC#コードにより、コンパイラはフォールト句を使用します。

public IEnumerable<string> GetSomeEnumerable()
{
    using (Disposable.Empty)
    {
        yield return DoSomeWork();
    }
}

dotPeekを使用して生成されたアセンブリを逆コンパイルし、[コンパイラで生成されたコードを表示する]オプションをオンにすると、fault句が表示されます。

bool IEnumerator.MoveNext()
{
    try
    {
        switch (this.<>1__state)
        {
        case 0:
            this.<>1__state = -1;
            this.<>7__wrap1 = Disposable.Empty;
            this.<>1__state = 1;
            this.<>2__current = this.<>4__this.DoSomeWork();
            this.<>1__state = 2;
            return true;
        case 2:
            this.<>1__state = 1;
            this.<>m__Finally2();
            break;
        }
        return false;
    }
    __fault
    {
        this.System.IDisposable.Dispose();
    }
}

通常、usingステートメントがtry / finalyブロックにマップされる場合、これはイテレーターブロックには意味がありません。try/finallyは最初の値が生成された後にDisposeします。

ただし、DoSomeWorkが例外をスローした場合は、破棄する必要があります。したがって、ここではフォールトハンドラーが役立ちます。例外が発生した場合にのみDisposeを呼び出し、例外をバブルアップさせます。概念的には、これは破棄してから再スローするキャッチブロックに似ています。

于 2013-06-05T01:12:06.540 に答える
4

障害ブロックは、次のように言うのと同じです。

bool success;
try
{
    success = false;
    ... do stuff
    success = true; // Also include this immediately before any 'return'    
}
finally
{
    if (!success)
    {
        ... do "fault" stuff here
    }
}

これは、キャッチアンドリスローとは意味的に多少異なることに注意してください。特に、上記の実装では、例外が発生し、スタックトレースが行番号を報告している場合、例外が発生した行の番号が含まれ...do stuffます。対照的に、キャッチアンドリスローを使用する場合、スタックトレースはリスローの行番号を報告します。...do stuffへの2つ以上の呼び出しが含まれ、それらの呼び出しの1つが例外をスローする場合foo、失敗した呼び出しの行番号を知ることは役立つ可能性がありますが、キャッチアンドリスローはその情報を失います。

上記の実装の最大の問題は、ブロックをsuccess = true;終了する可能性のあるコード内のすべての場所に手動で追加する必要があることと、ブロックが保留中の例外を知る方法がないことです。私がドラザーを持っていた場合、tryブロックを終了させる(またはtryfinallyfinally (Exception ex)Exnullブロックが正常に終了した場合)。これにより、「成功」フラグを手動で設定する必要がなくなるだけでなく、クリーンアップコードで例外が発生した場合の適切な処理が可能になります。このような状況では、クリーンアップ例外を不明瞭にしないでください(元の例外が通常、呼び出し元のコードが予期していた状態を表している場合でも、クリーンアップの失敗はおそらくそうではなかった状態を表します)が、おそらく負けたくないでしょう。元の例外のいずれか(クリーンアップが失敗した理由に関する手がかりが含まれている可能性があるため)。ブロックが入力された理由をブロックに認識させ、ブロックがそのような情報をクリーンアップコードで利用できるようにするためfinallyの拡張バージョンを含めることで、そのような状況をクリーンに解決できるようになります。IDisposableusing

于 2012-08-16T15:56:33.197 に答える