40

私は C# で作業していますが、usingブロックを使用して を実装するオブジェクトを宣言することについてはかなり怠慢IDisposableでした。しかし、いつ滑っているかを簡単に知る方法はありません。Visual Studio はこれをまったく示していないようです (何か足りないのでしょうか?)。何かを宣言するたびにヘルプをチェックして、どのオブジェクトが使い捨てで、どのオブジェクトが使い捨てでないかについて百科事典的な記憶を徐々に構築することになっているのでしょうか? 不必要で、苦痛で、エラーが発生しやすいようです。

これをどのよう処理しますか?

編集:

関連する質問のサイドバーを見て、とにかくオブジェクトのファイナライザーによって呼び出されることになっていることを明確にする別の質問を見つけました。Dispose()したがって、自分で呼び出さなくても、最終的には発生するはずです。つまり、使用しなくてもメモリリークは発生しませんusing(これは、私がずっと心配していたことだと思います)。唯一の注意点は、ガベージ コレクターは、オブジェクトが管理されていないものとして保持している余分なメモリの量を知らないため、オブジェクトを収集することによって解放されるメモリの量を正確に把握できないことです。これにより、ガベージ コレクターのパフォーマンスが通常よりも低下します。

要するに、私が逃したとしても、それは世界の終わりではありませんusing. 何かが少なくとも警告を生成することを願っています。

(トピック外: 別の質問にリンクするための特別なマークダウンがないのはなぜですか?)

編集:

わかりました、わかりました、騒ぎ立てるのはやめてください。それは、コールするのが非常に重要な、すべてが発射された劇的なシマリスレベルです。Dispose()

今。それを考えると、なぜそんなに簡単に — 地獄、なぜそれが許されているのでしょう — 間違ったことをするのですか? あなたはそれを正しく行うために最善を尽くさなければなりません。他のすべてのようにそれを行うと、ハルマゲドンが発生します(明らかに)。カプセル化はここまでですよね?

[立ち去る、うんざり]

4

12 に答える 12

24

FxCopが役立つかもしれません(ただし、実行したばかりのテストは検出されませんでした)。しかし、はい:あなたはチェックするつもりです。IDisposableは、システムの重要な部分であるため、この習慣を身に付ける必要があります。IntelliSense を使用して検索すること.Dは、良い出発点です (ただし、完全ではありません)。

ただし、破棄が必要な型に慣れるのにそれほど時間はかかりません。一般に、たとえば、外部のもの (接続、ファイル、データベース) に関係するもの。

ReSharper もこの仕事を行い、「コンストラクトを使用する」オプションを提供します。ただし、エラーとしては提供されません...

もちろん、確信が持てない場合は、試し usingてみてください。偏執的であると、コンパイラーはあざけるように笑います。

using (int i = 5) {}

Error   1   'int': type used in a using statement must be implicitly convertible to 'System.IDisposable'    
于 2008-10-31T21:11:00.817 に答える
15

オブジェクトがIDisposableインターフェイスを実装する場合、それは理由があり、それを呼び出すことを意図しており、オプションと見なすべきではありません。これを行う最も簡単な方法は、usingブロックを使用することです。

Dispose()オブジェクトのファイナライザーによってのみ呼び出されることを意図したものではありません。実際、多くのオブジェクトが実装されますDispose()が、ファイナライザーは実装されません (これは完全に有効です)。

Dispose パターンの背後にある全体的な考え方は、オブジェクト (またはその継承チェーン内の任意のオブジェクト) によって維持されているアンマネージリソースを解放するためのある程度決定論的な方法を提供しているということです。Dispose()適切に呼び出さないと、絶対にメモリ リーク (またはその他の問題) が発生する可能性があります。

このDispose()メソッドは、デストラクタとはまったく関係ありません。.NET でデストラクタに最も近いのはファイナライザです。using ステートメントは割り当て解除を行いません...実際、呼び出しDispose()はマネージ ヒープの割り当て解除を行いません。割り当てられたアンマネージ リソースのみを解放します。マネージド リソースは、GC が実行され、そのオブジェクト グラフに割り当てられたメモリ領域が収集されるまで、実際には割り当て解除されません。

クラスが実装しているかどうかを判断する最良の方法は次のIDisposableとおりです。

  • IntelliSense (Dispose()またはClose()メソッドがある場合)
  • MSDN
  • リフレクター
  • コンパイラ (実装していない場合IDisposable、コンパイラ エラーが発生します)
  • 常識(完了後にオブジェクトを閉じる/解放する必要があると思われる場合は、おそらくそうする必要があります)
  • セマンティクス ( が存在する場合Open()、おそらくClose()呼び出されるべき対応する が存在します)
  • コンパイラ。usingステートメントに入れてみてください。IDisposable を実装していない場合、コンパイラはエラーを生成します。

Dispose パターンは、スコープの有効期間の管理に関するものと考えてください。リソースをできるだけ最後に取得し、できるだけ早く使用し、できるだけ早く解放したいと考えています。Dispose()using ステートメントは、例外があってもへの呼び出しが確実に行われるようにすることで、これを行うのに役立ちます。

于 2008-11-01T03:38:35.447 に答える
4

要するに、使用を逃しても世界の終わりではありません。何かが少なくとも警告を生成することを願っています。

ここでの問題は、IDisposable をusingブロックにラップするだけでは常に処理できるとは限らないことです。オブジェクトをもう少し長くぶらぶらさせる必要がある場合があります。その場合、そのDisposeメソッドを自分で明示的に呼び出す必要があります。

この良い例は、クラスがプライベート EventWaitHandle (または AutoResetEvent)を使用して 2 つのスレッド間で通信し、スレッドが終了したら WaitHandle を破棄したい場合です。

usingしたがって、ブロック内で IDisposable オブジェクトのみを作成することを確認するだけのツールほど単純ではありません。

于 2009-03-19T17:48:51.180 に答える
4

これが、(IMHO) C++ の RAII が .NET のusingステートメントよりも優れている理由です。

多くの人がIDisposable、これは管理されていないリソースのみを対象としていると言っていましたが、これは「リソース」の定義方法に応じてのみ当てはまります。読み取り/書き込みロックを実装することができIDisposable、「リソース」はコードブロックへの概念的なアクセスです。コンストラクターでカーソルを砂時計に変更し、以前に保存した値に戻すオブジェクトを作成するとIDispose、「リソース」が変更されたカーソルになります。スコープがどのように離れていても、スコープを離れるときに決定論的なアクションを実行したい場合は IDisposable を使用すると言えますが、「管理されていないリソース管理を管理するためのもの」と言うよりもはるかにキャッチーではないことを認めなければなりません。

なぜ .NET に RAII がないのかについての質問も参照してください。

于 2008-11-02T17:04:23.010 に答える
3

@Atario、受け入れられた答えが間違っているだけでなく、あなた自身の編集も間違っています。次の状況を想像してください ( Visual Studio 2005 の 1 つの CTP で実際に発生しました)。

グラフィックスを描画するには、ペンを破棄せずに作成します。ペンは大量のメモリを必要としませんが、内部で GDI+ ハンドルを使用します。ペンを破棄しないと、GDI+ ハンドルは解放されません。アプリケーションがメモリを集中的に使用しない場合、GC が呼び出されずにかなりの時間が経過する可能性があります。ただし、使用可能な GDI+ ハンドルの数は制限されており、すぐに新しいペンを作成しようとすると操作が失敗します。

実際、Visual Studio 2005 CTP では、アプリケーションを長時間使用すると、すべてのフォントが突然「システム」に切り替わります。

これこそまさに、廃棄を GC に頼るだけでは不十分な理由です。メモリ使用量は、取得する (そして解放しない) アンマネージド リソースの数と必ずしも相関するとは限りません。したがって、これらのリソースは、GC が呼び出されるずっと前に使い果たされる可能性があります。

さらに、もちろん、これらのリソースには、他のアプリケーションが適切に動作するのを妨げる可能性のある副作用 (アクセス ロックなど) があります。

于 2008-11-02T15:49:30.277 に答える
2

Using ブロックの一般的な使用法に追加するものは何もありませんが、ルールに例外を追加したかっただけです。

IDisposable を実装するオブジェクトは、その Dispose() メソッド中に例外をスローすべきではないようです。これは WCF (他にもあるかもしれません) まで完全に機能し、現在は Dispose() 中に WCF チャネルによって例外がスローされる可能性があります。これが Using ブロックで使用されたときに発生すると、問題が発生し、例外処理の実装が必要になります。これには明らかに内部の仕組みに関するより多くの知識が必要です。これが、IDisposable を実装しているにもかかわらず、Microsoft が Using ブロックで WCF チャネルを使用しないことを推奨するようになった理由です (申し訳ありませんが、リンクが見つかりませんでしたが、Google で他の多くの結果が見つかりました)。複雑!

于 2008-11-05T09:10:32.467 に答える
1

残念ながら、FxCop も StyleCop もこれについて警告していないようです。他のコメンテーターが言及したように、通常は必ず dispose を呼び出すことが非常に重要です。よくわからない場合は、常にオブジェクト ブラウザ (Ctrl+Alt+J) をチェックして、継承ツリーを確認します。

于 2008-11-02T18:15:22.230 に答える
1

私は主にこのシナリオでブロックを使用します。

外部オブジェクトを使用しています (通常、私の場合は IDisposable でラップされた COM オブジェクトです)。オブジェクト自体の状態が原因で例外がスローされたり、コードへの影響が原因で、さまざまな場所で例外がスローされたりする場合があります。一般に、現在のメソッド以外のコードが動作することを信頼していません。

議論のために、メソッドに 11 個の出口ポイントがあり、そのうち 10 個がこの using ブロック内にあり、1 個がその後にあるとします (これは、私が作成した一部のライブラリ コードでは一般的です)。

オブジェクトは using ブロックを終了するときに自動的に破棄されるため、.Dispose() を 10 回呼び出す必要はありません。これにより、dispose 呼び出しで乱雑になることが少なくなるため、コードがよりクリーンになります (この場合、コードの行数が最大 10 行減ります)。

また、dispose を呼び出すのを忘れた場合に誰かが私の後にコードを変更することで、IDisposable リーク バグ (見つけるのに時間がかかる可能性があります) が発生するリスクも少なくなります。これは、using ブロックでは必要ないためです。

于 2008-11-02T19:34:18.030 に答える
0

常に「使用中」のブロックを使用するようにしてください。ほとんどのオブジェクトでは、大きな違いはありませんが、最近、クラスにActiveXコントロールを実装し、Disposeが正しく呼び出されない限り、正常にクリーンアップされないという問題が発生しました。肝心なのは、それがあまり違いをもたらさないように見えても、それがいつか違いを生むので、正しくそれを行うようにしてください。

于 2008-11-01T03:13:26.183 に答える
0

このリンクによると、CodeRushアドインは、入力時にローカルIDisposable変数がリアルタイムでクリーンアップされていない場合に、検出してフラグを立てます。

あなたの探求の途中であなたに会うことができます。

于 2010-02-10T05:39:17.967 に答える
0

Fxcop (関連している) のように、VS のコード分析ツール (上位エディションのいずれかを持っている場合) もこれらのケースを見つけます。

于 2008-10-31T21:16:38.387 に答える
-1

あなたの質問の要点がわかりません。ガベージ コレクターのおかげで、メモリ リークが発生することはほとんどありません。ただし、堅牢なロジックが必要です。

IDisposable私は次のようなクラスを作成するために使用します:

public MyClass: IDisposable
{

    private bool _disposed = false;

    //Destructor
    ~MyClass()
    { Dispose(false); }

    public void Dispose()
    { Dispose(true); }

    private void Dispose(bool disposing)
    {
        if (_disposed) return;
        GC.SuppressFinalize(this);

        /* actions to always perform */

        if (disposing) { /* actions to be performed when Dispose() is called */ }

        _disposed=true;
}

これで、use ステートメントを見逃したとしてもusing、オブジェクトは最終的にガベージ コレクションされ、適切な破棄ロジックが実行されます。スレッドの停止、接続の終了、データの保存など、必要なことは何でもかまいません (この例では、リモート サービスの登録を解除し、必要に応じてリモート削除呼び出しを実行します)。

[編集] 明らかに、できるだけ早く Dispose を呼び出すことは、アプリケーションのパフォーマンスに役立ち良い習慣です。しかし、私の例のおかげで、Dispose を呼び出すのを忘れた場合、最終的には Dispose が呼び出され、オブジェクトがクリーンアップされます。

于 2011-01-19T15:39:58.633 に答える