2012-04-24 17 views
2

グローバルな値を変更してレジストリ値をそのまま使用して、元の状態に戻しようとすると、私は書くコードに繰り返し問題があります。オブジェクトを作成の逆の順序で廃棄しますか?

私はこの問題を解決するために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で試してみると思っていましたが、他のソリューションのためにすべての耳です。

+0

「デストラクタは設定を元に戻します」ということはどういう意味ですか?ディスポーザブルはデストラクタではなく、どんな場合でも毎回呼び出されます。エラーがない場合でも、毎回設定を元に戻すのはなぜですか?何か不足していますか? –

+0

複数の 'を使用する場合....複数の編集のためにうまくいくでしょう....? –

+0

どのように行っても、元に戻すことが明白でない場合は、多くの混乱とダウンストリームのバグが発生します。 – Marc

答えて

3

IDisposableをあなたが記述しているものを、このやり方でロールバックをguaranteしないでロールバックです。廃棄()メソッドは、むしろそれがこの

のように行うことができるオブジェクトが保持しているネイティブリソース(ネットワーク接続、ファイルなど)の状態に戻す

しかし途中で解除するための責任があり、その目的のためのものではありません

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); 
} 

リソースが廃棄されたため、リソースを使用して実行された処理が元に戻りません。データベース接続を廃棄すると、処理が取り消されず、オブジェクトが破棄されます。

あなたがそのメソッドの誤用であるロールバック・コードと廃棄を()、オーバーライドしない限り、それはあなたの価値を引き出していないだろう、それはプログラマとしてのあなたresonsibilityです。 .NETドキュメントは、そのようなことで開催されたファイルは、 ストリーム、ハンドルなどのアンマネージリソースを閉じたり、解放するために、このメソッドを使用しDispose()

ために次のことを述べているので、私はそれがDisposeメソッドの誤用であると言う理由があります このインタフェースを実装するクラスのインスタンスです。慣例により、このメソッドは、オブジェクトが保持するリソースを解放するか、または再利用のためにオブジェクトを準備するすべてのタスクに使用されます。

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

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

+0

"あなたが(そのメソッドの悪用である)ロールバックコードでDispose()をオーバーライドしない限り、プログラマーとしてのあなたのやりとりであるあなたの値を引き出しません。" - これはまさに私がやっていたことでした。どのように誤用されているかを詳しく説明できますか? – Sperry

+0

確かに私は私の答えを適切に編集します –

+0

私は私の答えに明確さを加えました。 –

0

RegistryModificationオブジェクトを取得する代わりに、コンストラクタの静的なファクトリメソッドを使用して、アクションの静的なスタックを持っている(クラスが理解できるRegistryModifcationのいくつかの単純化した表現が、全体を必要としません。そのオブジェクトが廃棄されたかどうかを示す)、RegistryModificationクラスのオブジェクトです。新しいものを生成するときは、スタックに表現を張ってください。処理するときは、配置されたオブジェクトを表すように表現をマークし、スタック内のアクションを上から下に戻します(処理されていないオブジェクトからアクションを見つけたら停止します)。

処分で解放しようとしているメモリをどれくらい使っているかは分かりませんが、それはうまくいくはずです。

0

パターン実装するための2つの一般的なパターンがあります:「何かをする準備は、それを行う、クリーンアップ」:それは使用パターンが可能になりますので、

 
// Pattern #1 

void FancyDoSomething(MethodInvoker ThingToDo) 
{ 
    try 
    { 
    PrepareToDoSomething(); 
    ThingToDo.Invoke(); // The ".Invoke" is optional; the parens aren't. 
    } 
    finally 
    { 
    Cleanup(); 
    } 
} 

void myCode(void) 
{ 
    FancyDoSomething(() => {Stuff to do goes here}); 
} 

// Pattern #2: 

// Define an ActionWrapper so its constructor prepares to do something 
// and its Dispose method does the required cleanup. Then... 

void myCode(void) 
{ 
    using(var wrap = new ActionWrapper()) 
    { 
    Stuff to do goes here 
    } 
} 

パターン#2は、いくつかの方法で、より汎用性があります厳重なネスティングルールに従わない。これは、IEnumerator<T>がデリゲートをとり、すべてのリスト項目でそれを呼び出す列挙メソッドを単に持つのではなく、そのパターンを使用する理由の一部です。列挙子とのネストセマンティクスに従わなければならない場合、リストマージのようなことをしようとすると、なんとなく厄介なことになります。

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

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

関連する問題