16

Dispose()「using」ボディが正常に終了したか例外で終了したかによってメソッドが異なる処理を行う特別なクラスで「using」節を使用したい C# のコーディング パターンを調査しています。

私の理解では、CLR は現在処理中の例外を追跡し、それが "catch" ハンドラーによって消費されるまで追跡します。ただし、コードがアクセスできるように、この情報が何らかの方法で公開されているかどうかは完全には明らかではありません。そうであるかどうか、もしそうなら、アクセス方法を知っていますか?

例えば:

using (var x = new MyObject())
{
    x.DoSomething();
    x.DoMoreThings();
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        if (ExceptionIsBeingHandled)
            Rollback();
        else
            Commit();
    }
}

System.Transactions.TransactionScopeこれは、 の呼び出しによって成功/失敗が決定されるのではなく、本体が正常に終了x.Complete()したかどうかに基づいて決定されることを除いて、にほとんど似ています。using

4

5 に答える 5

13

https://www.codewrecks.com/post/old/2008/07/detecting-if-finally-block-is-executing-for-an-manhandled-exception/は、コードが実行されたかどうかを検出するための「ハック」について説明しています例外処理モードかどうか。Marshal.GetExceptionPointersを使用して、例外が「アクティブ」かどうかを確認します。

ただし、次の点に注意してください。

備考

GetExceptionPointers は、構造化例外処理 (SEH) のみのコンパイラ サポートのために公開されています。注注:

このメソッドは SecurityAction.LinkDemand を使用して、信頼されていないコードから呼び出されないようにします。SecurityPermissionAttribute.UnmanagedCode アクセス許可を持つ必要があるのは、直前の呼び出し元だけです。部分的に信頼されたコードからコードを呼び出すことができる場合は、ユーザー入力を検証せずに Marshal クラス メソッドに渡さないでください。LinkDemand メンバーの使用に関する重要な制限については、Demand vs. LinkDemand を参照してください。

于 2009-11-29T13:23:46.810 に答える
8

質問への答えではありませんが、実際のコードで「受け入れられた」ハックを使用することは決してなかったので、「実際に」テストされていないことに注意してください。代わりに、次のようなものを選びました。

DoThings(x =>
{
    x.DoSomething();
    x.DoMoreThings();
});

どこ

public void DoThings(Action<MyObject> action)
{
    bool success = false;
    try
    {
        action(new MyObject());
        Commit();
        success = true;
    }
    finally
    {
        if (!success)
            Rollback();
    }
}

重要な点は、質問の「使用」の例と同じくらいコンパクトで、ハックを使用しないことです。

欠点の中には、パフォーマンスのペナルティ(この場合は完全に無視できる)と、F10DoThingsが実際にに直接ステップインしたいときにステップインすることがありx.DoSomething()ます。どちらも非常にマイナーです。

于 2010-03-09T23:06:21.550 に答える
4

この情報は利用できません。

DbTransaction クラスで使用されるパターンと同様のパターンを使用します。つまり、IDisposable クラスは、DbTransaction.Commit() に類似したメソッドを実装する必要があります。Dispose メソッドは、Commit が呼び出されたかどうかに応じて異なるロジックを実行できます (DbTransaction の場合、明示的にコミットされていない場合、トランザクションはロールバックされます)。

クラスのユーザーは、典型的な DbTransaction と同様に、次のパターンを使用します。

using(MyDisposableClass instance = ...)
{
    ... do whatever ...

    instance.Commit();
} // Dispose logic depends on whether or not Commit was called.

編集このパターンを認識していることを示すために質問を編集したことがわかります(例ではTransactionScopeを使用しています)。それでも、それが唯一の現実的な解決策だと思います。

于 2009-11-29T13:20:49.733 に答える
2

これはそれほど悪い考えではないようです。C#/.NET では理想的ではないようです。

C++ には、コードが例外のために呼び出されているかどうかをコードが検出できるようにする関数があります。これは、RAII デストラクタで最も重要です。制御フローが通常か例外かに応じて、デストラクタがコミットまたは中止を選択するのは簡単なことです。これはかなり自然なアプローチだと思いますが、組み込みのサポートの欠如 (および回避策の道徳的に疑わしい性質; 私にはかなり実装に依存しているように感じます) は、おそらくより従来のアプローチを採用する必要があることを意味します.

于 2009-11-29T15:06:21.423 に答える
2

using ステートメントは、try finally ブロックのシンタックス シュガーにすぎません。try finally out を完全に記述してから、catch ステートメントを追加して特殊なケースを処理することで、必要なものを取得できます。

try
{
    IDisposable x = new MyThing();
}
catch (Exception exception) // Use a more specific exception if possible.
{
    x.ErrorOccurred = true; // You could even pass a reference to the exception if you wish.
    throw;
}
finally
{
    x.Dispose();
}

必要に応じて MyThing 内でこれを行うことができます。たとえば、次のようにします。

class MyThing : IDisposable
{
    public bool ErrorOccurred() { get; set; }

    public void Dispose()
    {
        if (ErrorOccurred) {
            RollBack();
        } else {
            Commit();
        }
    }
}

注: また、なぜこれを行うのか疑問に思う必要があります。コードの匂いがします。Dispose メソッドは、例外を処理するためではなく、アンマネージ リソースをクリーンアップするためのものです。おそらく、dispose ではなく catch ブロックに例外処理コードを記述したほうがよいでしょう。コードを共有する必要がある場合は、両方の場所から呼び出すことができる便利なヘルパー関数をいくつか作成してください。

あなたが望むことをするためのより良い方法は次のとおりです:

using (IDisposable x = new MyThing())
{
    x.Foo();
    x.Bar();
    x.CommitChanges();
}

class MyThing : IDisposable
{
    public bool IsCommitted { get; private set; }

    public void CommitChanges()
    {
        // Do stuff needed to commit.
        IsCommitted = true;
    }

    public void Dispose()
    {
        if (!IsCommitted)
            RollBack();
    }
}
于 2009-11-29T13:06:38.987 に答える