37

finally例外がスローされた結果として、コードがハンドラーのコンテキストで現在実行されているかどうかを判断することは可能ですか?私はこのIDisposableパターンを使用して入口/出口のスコーピング機能を実装するのが好きですが、このパターンに関する1つの懸念は、の本体で例外が発生した場合にスコープの終わりの動作を必ずしも発生させたくない場合があることですusing。私はこのようなものを探しているでしょう:

public static class MyClass
{
    public static void MyMethod()
    {
        using (var scope = MyScopedBehavior.Begin())
        {
            //Do stuff with scope here
        }
    }
}

public sealed class MyScopedBehavior : IDisposable
{
    private MyScopedBehavior()
    {
        //Start of scope behavior
    }

    public void Dispose()
    {
        //I only want to execute the following if we're not unwinding
        //through finally due to an exception:
        //...End of scope behavior    
    }

    public static MyScopedBehavior Begin()
    {
        return new MyScopedBehavior();
    }
}

これを実現する方法は他にもありますが(特定の動作で呼び出しを囲む関数にデリゲートを渡す)、IDisposableパターンを使用してそれを実行できるかどうか知りたいです。


実際、これは明らかにここの前に尋ねられ、答えられました。非常にハック的な方法で検出することが可能です。私は実際にはそのテクニックを使用しませんが、それが可能であることを知るのは興味深いことです。

4

8 に答える 8

17

私が見たこれを達成するための手段は、追加の方法を必要とします:

public static void MyMethod()
{
    using (var scope = MyScopedBehavior.Begin())
    {
        //Do stuff with scope here
        scope.Complete(); // Tells the scope that it's good
    }
}

これを行うことにより、スコープオブジェクトは、エラーが原因で破棄されているのか、操作が成功したのかを追跡できます。これは、たとえばTransactionScopeが採用しているアプローチです( TransactionScope.Completeを参照)。

于 2010-07-21T16:27:10.260 に答える
15

副次的な点として、ILでは、例外がスローされた場合にのみ入力されるSEHfaultブロックに類似しているものを指定できます。ここで、ページの約2/3下に例を示します。残念ながら、C#はこの機能を公開していません。finally

于 2010-07-21T16:44:58.650 に答える
7

私は単体テストに似たものを探していました-テスト実行後にオブジェクトをクリーンアップするために使用するヘルパークラスがあり、「using」構文をきれいに保ちたいと思っています。また、テストが失敗した場合にクリーンアップしないオプションも必要でした。私が思いついたのは、Marshal.GetExceptionCode()を呼び出すことです。これがすべての場合に適切かどうかはわかりませんが、テストコードでは問題なく機能するようです。

于 2010-10-07T17:19:26.750 に答える
5

私が思いつくことができる最高のものは次のとおりです。

using (var scope = MyScopedBehavior.Begin())
{
  try
  {
    //Do stuff with scope here
  }
  catch(Exception)
  {
    scope.Cancel();
    throw;
  }
}

もちろん、scope.Cancel()Dispose()で何も起こらないことを確認します

于 2010-07-21T16:27:42.833 に答える
4

次のパターンは、APIの誤用、つまりスコープ完了メソッドが呼び出されない、つまり完全に省略される、または論理的な条件のために呼び出されないという問題を回避します。これはあなたの質問にもっと密接に答え、APIユーザーにとってはさらに少ないコードだと思います。

編集

ダンのコメントの後、さらに簡単に:

public class Bling
{
    public static void DoBling()
    {
        MyScopedBehavior.Begin(() =>
        {
            //Do something.
        }) ;
    }   
}

public static class MyScopedBehavior
{
    public static void Begin(Action action)
    {
        try
        {
            action();

            //Do additonal scoped stuff as there is no exception.
        }
        catch (Exception ex)
        {
            //Clean up...
            throw;
        }
    }
}   
于 2010-07-21T16:49:46.323 に答える
1

try/catch/finally書き込み句を手動で使用するのが最善の方法だと思います。最初の「Effectivec#」の本の項目を調べてください。優れたC#ハッカーは、usingが何に拡張されるかを正確に知っている必要があります。.Net1.1から少し変更されています。糖分を含まないコードを研究します。

次に、独自のコードを作成する場合は、またはを使用するか、独自のコードを作成しますusing。それほど難しいことではなく、知っておくとよいことです。

あなたは他のトリックに夢中になるかもしれませんが、それは重すぎて、効率的でさえありません。コードサンプルを含めましょう。

レイジーウェイ

using (SqlConnection cn = new SqlConnection(connectionString))
using (SqlCommand cm = new SqlCommand(commandString, cn))
{
    cn.Open();
    cm.ExecuteNonQuery();
}

手動の方法

bool sawMyEx = false;
SqlConnection cn =  null;
SqlCommand cm = null;

try
{
    cn = new SqlConnection(connectionString);
    cm = new SqlCommand(commandString, cn);
    cn.Open();
    cm.ExecuteNonQuery();
}
catch (MyException myEx)
{
    sawMyEx = true; // I better not tell my wife.
    // Do some stuff here maybe?
}
finally
{
    if (sawMyEx)
    {
        // Piss my pants.
    }

    if (null != cm);
    {
        cm.Dispose();
    }
    if (null != cn)
    {
        cn.Dispose();
    }
}
于 2010-07-21T16:26:30.870 に答える
1

実行時に保留中の例外がある場合はそれを示すパラメーターをメソッドが受け入れるIDisposableバリアントがあると、(IMHOは非常に)役立ちます。Dispose特にDispose、予期されたクリーンアップを実行できない場合は、以前の例外に関する情報を含む例外をスローできます。またDispose、コードがブロック内で実行するはずの処理を「忘れた」場合にメソッドが例外をスローできるようにしusingますが、usingブロックが途中で終了する可能性のある他の例外を上書きすることはできません。残念ながら、そのような機能はまだ存在しません。

保留中の例外が存在するかどうかを確認するためにAPI関数を使用する方法を提案する記事は多数あります。finallyこのようなアプローチの大きな問題の1つは、正常に完了したブロックでコードが実行されている可能性があるが、途中で終了したブロックtryにネストされている可能性があることです。そのような状況が存在することをメソッドが識別できたとしても、それが「属している」ブロックを知る方法はありません。どちらかの状況が当てはまる例を定式化することができます。finallytryDisposetry

現状では、おそらく最善のアプローチは、明示的な「成功」メソッドを用意し、呼び出されない場合は失敗を想定することです。「成功」メソッドの呼び出しを忘れた場合の結果は、例外がスローされなくても明らかであるはずです。単純なユーティリティメソッドとして役立つ可能性のあるものの1つは、次のようなものです。

T Success<T>(T returnValue)
{
  Success();
  return T;
}

したがって、次のようなコードを許可します。

return scopeGuard.Success(thingThatMightThrow());

それよりも

var result = thingThatMightThrow();
scopeGuard.Success();
return result;
于 2013-06-05T15:59:05.567 に答える
0

最後にブロックの内側から単純に廃棄しtry { }、最後に使用しないのはなぜですか?これはあなたが探している行動のようです。

これは、他の人があなたのクラスをどのように使用するかという点でもより現実的です。例外が発生した場合に、これを使用したことのある人は誰もが処分したくないと確信していますか?または、この動作はクラスのコンシューマーによって処理される必要がありますか?

于 2010-07-21T16:30:15.100 に答える