38

IDisposable.Disposeメソッドには、例外がスローされているかどうかを判断する方法がありますか?

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
}

ステートメントで例外がスローされた場合、オブジェクトが破棄されusingたときにそれについて知りたいです。IDisposable

4

11 に答える 11

23

IDisposableメソッドで拡張しComplete、そのようなパターンを使用できます。

using (MyWrapper wrapper = new MyWrapper())
{
    throw new Exception("Bad error.");
    wrapper.Complete();
}

usingステートメント内で例外がスローされた場合、Complete前に呼び出されませんDispose

スローされた正確な例外を知りたい場合は、イベントをサブスクライブし、最後にスローされた例外を変数AppDomain.CurrentDomain.FirstChanceExceptionに保存します。ThreadLocal<Exception>

TransactionScopeクラスで実装されたそのようなパターン。

于 2013-01-14T15:37:19.310 に答える
20

いいえ、.Netフレームワークでこれを行う方法はありません。finally句でスローされている現在の例外を把握することはできません。

私のブログのこの投稿を参照してください。Rubyの同様のパターンとの比較については、IDisposableパターンに存在すると思われるギャップを強調しています。

Ayendeには、発生した例外を検出できるトリックがありますが、それがどの例外であったかはわかりません。

于 2008-10-21T10:50:01.583 に答える
6

メソッドで例外をキャプチャすることはできませんDispose()

ただし、Marshal.GetExceptionCode()Disposeをチェックインして、例外が発生したかどうかを検出することは可能ですが、私はそれに依存しません。

クラスが不要で、例外をキャプチャしたいだけの場合は、次のように、try/catchブロック内で実行されるラムダを受け入れる関数を作成できます。

HandleException(() => {
    throw new Exception("Bad error.");
});

public static void HandleException(Action code)
{
    try
    {
        if (code != null)
            code.Invoke();
    }
    catch
    {
        Console.WriteLine("Error handling");
        throw;
    }
}

例として、トランザクションのCommit()またはRollback()を自動的に実行し、ログを記録するメソッドを使用できます。このように、try/catchブロックが常に必要なわけではありません。

public static int? GetFerrariId()
{
    using (var connection = new SqlConnection("..."))
    {
        connection.Open();
        using (var transaction = connection.BeginTransaction())
        {
            return HandleTranaction(transaction, () =>
            {
                using (var command = connection.CreateCommand())
                {
                    command.Transaction = transaction;
                    command.CommandText = "SELECT CarID FROM Cars WHERE Brand = 'Ferrari'";
                    return (int?)command.ExecuteScalar();
                }
            });
        }
    }
}

public static T HandleTranaction<T>(IDbTransaction transaction, Func<T> code)
{
    try
    {
        var result = code != null ? code.Invoke() : default(T);
        transaction.Commit();
        return result;
    }
    catch
    {
        transaction.Rollback();
        throw;
    }
}
于 2012-04-19T20:52:15.890 に答える
3

ジェームズ、wrapperできることは、独自の例外をログに記録することだけです。wrapperの消費者に独自の例外をログに記録させることはできません。それは IDisposable の目的ではありません。IDisposable は、オブジェクトのリソースを半決定論的に解放するためのものです。正しい IDisposable コードを記述することは簡単ではありません。

実際、クラスのコンシューマーは、クラスの dispose メソッドを呼び出す必要さえなく、using ブロックを使用する必要もないため、すべてがうまくいきません。

ラッパー クラスの観点から見ると、それが using ブロック内に存在し、例外があったことを気にする必要があるのはなぜでしょうか? それはどんな知識をもたらしますか?サードパーティのコードが例外の詳細とスタック トレースに関与することはセキュリティ リスクですか? wrapper計算にゼロ除算がある場合はどうすればよいですか?

IDisposable に関係なく、例外をログに記録する唯一の方法は、try-catch を実行してから、catch で再スローすることです。

try
{
    // code that may cause exceptions.
}
catch( Exception ex )
{
   LogExceptionSomewhere(ex);
   throw;
}
finally
{
    // CLR always tries to execute finally blocks
}

外部 API を作成しているとのことです。例外がコードから発生したことをログに記録するには、API の公開境界ですべての呼び出しを try-catch でラップする必要があります。

パブリック API を作成している場合は、Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (Microsoft .NET Development Series) - 2nd Edition .. 1st Editionを読む必要があります。


私はそれらを支持していませんが、IDisposable が他の興味深いパターンに使用されているのを見てきました。

  1. 自動ロールバック トランザクション セマンティクス。トランザクション クラスは、まだコミットされていない場合、Dispose でトランザクションをロールバックします。
  2. ロギング用のタイミング コード ブロック。オブジェクトの作成中にタイムスタンプが記録され、Dispose で TimeSpan が計算され、ログ イベントが書き込まれました。

* これらのパターンは、IDisposable セマンティクスをオーバーロードすることなく、簡単に間接化と匿名デリゲートの別のレイヤーで実現できます。重要な注意点は、IDisposable ラッパーは、あなたまたはチーム メンバーが適切に使用するのを忘れると役に立たないということです。

于 2008-10-21T20:42:17.237 に答える
2

「MyWrapper」クラスの Dispose メソッドを実装することで、この購入を行うことができます。dispose メソッドでは、次のように例外があるかどうかを確認できます。

public void Dispose()
{
    bool ExceptionOccurred = Marshal.GetExceptionPointers() != IntPtr.Zero
                             || Marshal.GetExceptionCode() != 0;
    if(ExceptionOccurred)
    {
        System.Diagnostics.Debug.WriteLine("We had an exception");
    }
}
于 2012-03-16T01:50:39.570 に答える
1

usingステートメントの構文糖衣構文の代わりに、このための独自のロジックを実装してみませんか。何かのようなもの:

try
{
  MyWrapper wrapper = new MyWrapper();

}
catch (Exception e)
{
  wrapper.CaughtException = true;
}
finally
{
   if (wrapper != null)
   {
      wrapper.Dispose();
   }
}
于 2008-10-20T23:03:46.953 に答える
1

現在、2017 年には、これが一般的な方法であり、例外のロールバックの処理が含まれます。

    public static T WithinTransaction<T>(this IDbConnection cnn, Func<IDbTransaction, T> fn)
    {
        cnn.Open();
        using (var transaction = cnn.BeginTransaction())
        {
            try
            {
                T res = fn(transaction);
                transaction.Commit();
                return res;
            }
            catch (Exception)
            {
                transaction.Rollback();
                throw;
            }
            finally
            {
                cnn.Close();
            }
        }
    }

そして、あなたはそれを次のように呼びます:

        cnn.WithinTransaction(
            transaction =>
            {
                var affected = ..sqlcalls..(cnn, ...,  transaction);
                return affected;
            });
于 2018-04-05T19:55:12.560 に答える
1

使い捨てオブジェクトが破棄されたときに例外がスローされたかどうかを確認できるだけでなく、finally 句内でスローされた例外を少し魔法で取得することもできます。私の ApiChange ツールのトレース ライブラリは、このメソッドを使用して、using ステートメント内の例外をトレースします。これがどのように機能するかについての詳細は、こちらをご覧ください

さようなら、アロイス・クラウス

于 2010-11-13T00:04:48.990 に答える
0

これにより、直接または dispose メソッド内でスローされた例外がキャッチされます。

try
{
    using (MyWrapper wrapper = new MyWrapper())
    {
        throw new MyException("Bad error.");
    }
}
catch ( MyException myex ) {
    //deal with your exception
}
catch ( Exception ex ) {
    //any other exception thrown by either
    //MyWrapper..ctor() or MyWrapper.Dispose()
}

しかし、これはこのコードを使用して彼らに依存しています - 代わりに MyWrapper にそれをさせたいようです。

using ステートメントは、Dispose が常に呼び出されるようにするためのものです。それは本当にこれをやっています:

MyWrapper wrapper;
try
{
    wrapper = new MyWrapper();
}
finally {
    if( wrapper != null )
        wrapper.Dispose();
}

あなたが望むのは次のように聞こえます:

MyWrapper wrapper;
try
{
    wrapper = new MyWrapper();
}
finally {
    try{
        if( wrapper != null )
            wrapper.Dispose();
    }
    catch {
        //only errors thrown by disposal
    }
}

Dispose の実装でこれに対処することをお勧めします。とにかく、Disposal 中の問題を処理する必要があります。

API のユーザーがリソースを何らかの方法で解放する必要がある場合は、Close()メソッドを用意することを検討してください。dispose もそれを呼び出す必要があります (まだ呼び出していない場合) が、API のユーザーがより細かい制御が必要な場合は、自分で呼び出すこともできます。

于 2008-10-21T20:54:25.090 に答える
0

純粋に .net 内にとどまりたい場合は、さまざまな部分のデリゲートを受け入れる「try-catch-finally」ラッパーを作成するか、受け入れる「using-style」ラッパーを作成することをお勧めします。呼び出されるメソッドと、完了後に破棄する必要がある 1 つ以上の IDisposable オブジェクト。

「using スタイル」ラッパーは、try-catch ブロックで破棄を処理できます。また、破棄で例外がスローされた場合は、破棄の失敗とメイン デリゲートで発生した例外を保持する CleanupFailureException でそれらをラップします。 、または元の例外で例外の「データ」プロパティに何かを追加します。通常、クリーンアップ中に発生する例外は、メインライン処理で発生する問題よりもはるかに大きな問題を示しているため、CleanupFailureException でラップすることをお勧めします。さらに、CleanupFailureException を記述して、複数のネストされた例外を含めることができます (「n」個の IDisposable オブジェクトがある場合、n+1 個のネストされた例外が存在する可能性があります。つまり、メインラインから 1 つと各 Dispose から 1 つです)。

メインラインが完了した場合でも、例外パラメーターが非 null になる可能性があること。純粋な C# コードはそのような状態を検出できませんでしたが、vb.net ラッパーは検出できました)。

于 2011-08-08T15:30:38.793 に答える