3

私が書いたコードで繰り返し発生する問題に直面しています。グローバル値を変更し(例としてレジストリ値を使用します)、変更を元の状態に戻そうとします。

この問題を解決するためにIDisposableを使用しようと思いました。作成されると、オブジェクトはレジストリ値を読み取り、ローカルに保存してから変更します。破壊されると、設定が元に戻ります。次のように使用されます。

using(RegistryModification mod = new RegistryModification("HKCR\SomeValue", 42))
{
    // reg value now modified to 42, do stuff
} // Object gets disposed, which contains code to revert back the setting

1つの変更のみが行われた場合は、うまく機能するはずです。しかし、複数の変更が行われた場合、または呼び出し元が'using'構文を使用してオブジェクトを作成しない場合、問題が発生していることがわかります。

public void Foo()
{
    // assume HKCR\SomeValue starts as '13'

    // First object reads HKCR\SomeValue and stores '13', then modifies to 42
    RegistryModification mod1 = new RegistryModification("HKCR\SomeValue", 42); 

    // Next object reads HKCR\SomeValue and stores '42', then modifies to 12
    RegistryModification mod2 = new RegistryModification("HKCR\SomeValue", 12);

}
// objects are destroyed. But if mod1 was destroyed first, followed by mod2,
// the reg value is set to 42 and not 13. Bad!!!

呼び出し元がオブジェクトを手動で破棄すると、問題はさらに悪化します。これにより、私のアプローチには欠陥があると思います。

この問題を解決するために受け入れられているパターンはありますか?クラスに静的スタックを追加すると役立つかもしれないと思っていました。

オブジェクトが破壊される順序は何らかの方法で保証されていますか?IDisposableを試してみようと思いましたが、他の解決策に耳を傾けています。

4

4 に答える 4

4

IDisposableは、この方法でロールバックを保証するものではありません。あなたが説明しているのはロールバックです。Dispose()メソッドは、その目的を目的としたものではなく、オブジェクトが保持するネイティブリソース(ネットワーク接続、ファイルなど)の解放を担当します。

ただし、状態に戻す方法は次のように行うことができます

public void Foo(SomeObjectType theObject)
{
    int initialValue = theObject.SomeProperty;
    theObject.SomeProperty = 25;
    Console.Out.WriteLine("Property is:" + theObject.SomeProperty);

    // reset object.
    theObject.SomeProperty = initialValue;
    Console.Out.WriteLine("Property oringinal value is:" + theObject.SomeProperty);
}

リソースが破棄されたからといって、それを使用して実行されたアクションが元に戻されるわけではありません。データベース接続を破棄しても、リソースで実行された作業は元に戻されず、オブジェクトが破棄されるだけです。

Dispose()をロールバックコードでオーバーライドしない限り、これはそのメソッドの誤用であり、値を引き出すことはありません。これは、プログラマーとしての責任です。disposeメソッドの誤用であると私が言う理由は、.netのドキュメントに次のように記載されているためです。Dispose()

このメソッドを使用して、このインターフェイスを実装するクラスのインスタンスによって保持されているファイル、ストリーム、ハンドルなどのアンマネージリソースを閉じたり解放したりします。慣例により、このメソッドは、オブジェクトが保持しているリソースの解放、またはオブジェクトの再利用の準備に関連するすべてのタスクに使用されます。

これは一般的に、たとえばハンドルを重いもの(たとえばGDIリソース)に解放することを意味します。メモリリークやアクセスの望ましくない結果を回避するために、ネイティブリソースを解放するか、場合によっては特定の状態にする必要があります。この場合、おそらくDisposeメソッドでレジストリを以前の状態に戻す必要があると言うのは簡単ですが、これはdisposeメソッドの意図ではないと私は考えています。目的は、リソースを解放し、それらを再び使用できる状態に戻すことです。あなたがしたいのは値をリセットすることです(これは本質的に別の設定操作です)。カスタムdisposeメソッドでその作業を行うことは、後でさまざまなコンテキストで再利用の機会をショートさせることも意味します。

私が言っているのは、変更が終了したら、オブジェクトを初期状態に戻すための明示的なコードを作成する必要があるということです。これをデータ構造(スタックなど)に格納し、複数の操作があった場合は値を読み戻すか、1つの操作に対して上記のような単純な方法を使用することができます。

于 2012-04-24T21:58:37.987 に答える
1

System.Collections.Generic.Stack<T>実装を使用したいようです。変更するたびに値がスタックにプッシュされ、「元に戻す」ごとに値がスタックからポップされます。

http://msdn.microsoft.com/en-us/library/3278tedw.aspx

于 2012-04-24T22:00:07.943 に答える
0

コンストラクターの代わりに静的ファクトリメソッドを使用してRegistryModificationオブジェクトを取得し、アクションの静的スタックを作成します(RegistryModifcationクラスが理解できるが、オブジェクト全体を表す必要がない、オブジェクトが破棄されたかどうかを示す、いくつかの簡略化された表現) )RegistryModificationクラスで。新しいものを生成するときは、スタックに表現を貼り付けます。破棄するときは、その表現を破棄されたオブジェクトを表すものとしてマークし、スタック内のアクションを上から下に逆にしてみます(破棄されていないオブジェクトからのアクションを見つけたら停止します)。

廃棄して解放しようとしているメモリの使用に関して、これにいくらかかるかはわかりませんが、機能するはずです。

于 2012-04-24T22:01:44.067 に答える
0

パターンを実装するための2つの一般的なパターンがあります:「何かをする準備をする;それをする;クリーンアップする」:

//パターン#1

void FancyDoSomething(MethodInvoker ThingToDo)
{{
  試す
  {{
    PrepareToDoSomething();
    ThingToDo.Invoke(); //「。Invoke」はオプションです。両親はそうではありません。
  }
  ついに
  {{
    掃除();
  }
}

void myCode(void)
{{
  FancyDoSomething(()=> {やるべきことはここにあります});
}

//パターン#2:

// ActionWrapperを定義して、そのコンストラクターが何かを実行する準備をするようにします
//そしてそのDisposeメソッドは必要なクリーンアップを行います。それで...

void myCode(void)
{{
  using(var wrap = new ActionWrapper())
  {{
    やることはここに行きます
  }
}

パターン#2は、厳密なネスト規則に従わない使用パターンを可能にするため、いくつかの点でより用途が広いです。IEnumerator<T>これは、デリゲートを取得してすべてのリストアイテムで呼び出す列挙メソッドを単に持つのではなく、そのパターンを使用する理由の一部です。列挙子を使用してネストのセマンティクスに従わなければならない場合にリストのマージのようなことを行おうとすると、せいぜい厄介です。

一方、一部の種類の保護されたリソースは、厳密にネストされた方法でのみ意味のある方法で使用できます。そのため、上記の最初のアプローチは、特定のスレッド内でネストのセマンティクスを厳密に適用するため、より適切な場合があります。

最初のアプローチを使用したくない場合はDisposing、保護されたリソースに関連付けられたインスタンスがその後に生成されたすべてのインスタンスを無効にするようにオブジェクトを配置することをお勧めします。Dispose「遅延驚愕」要因を最小限に抑えるために、最初のラッパーオブジェクトが作成されたときにリソースにスレッドアフィニティを割り当てさせ、すべてのラッパーがdになるまで他のスレッドによるアクセスを禁止することができます(スレッドがスレッドがオブジェクトが消える前にオブジェクトに悪いことをした可能性が十分に低いと思われる場合は、作成されたオブジェクトは存在しなくなります)。

于 2012-04-25T15:07:53.997 に答える