.NETのMemoryStreamが閉じていない場合、メモリリークは発生しますか?


112

私は次のコードを持っています:

MemoryStream foo(){
    MemoryStream ms = new MemoryStream();
    // write stuff to ms
    return ms;
}

void bar(){
    MemoryStream ms2 = foo();
    // do stuff with ms2
    return;
}

私が割り当てたMemoryStreamが何らかの理由で後で破棄​​されない可能性はありますか?

私はこれを手動で閉じると主張するピアレビューを持っています、そして彼が有効なポイントを持っているかどうかを知るための情報を見つけることができません。


41
レビュー担当者に閉じる必要があると彼が考えている理由を正確に尋ねてください。彼が一般的な良い習慣について話すならば、彼はおそらく賢いです。彼が以前に記憶を解放することについて話すならば、彼は間違っています。
Jon Skeet、

回答:


60

何かが使い捨ての場合は、常にそれを処分する必要があります。メソッドでusingステートメントを使用して、bar()確実ms2にDisposed にする必要があります。

最終的にはガベージコレクターによってクリーンアップされますが、常にDisposeを呼び出すことをお勧めします。コードでFxCopを実行すると、警告としてフラグが立てられます。


16
usingブロックの呼び出しによって破棄されます。
Nick、

20
@Grauenwolf:あなたの主張はカプセル化を破ります。コンシューマーとして、それが何もしないかどうかを気にする必要はありません。それがIDisposableである場合、Dispose()するのはあなたの仕事です。
Marc Gravell

4
これは、StreamWriterクラスには当てはまりません。StreamWriterを破棄した場合にのみ、接続されたストリームを破棄します。ガベージコレクションが行われ、ファイナライザが呼び出された場合、ストリームは破棄されません。これは仕様によるものです。
springy76

4
この質問は2008年からだったと思いますが、今日は.NET 4.0タスクライブラリがあります。処分は()である不要、ほとんど の場合タスクを使用した場合。IDisposable 「終了時にこれを破棄する方がよい」という意味であることに同意しますが、それが実際に意味しているわけではありません。
Phil

7
IDisposableオブジェクトを破棄してはいけないもう1つの例は、HttpClient aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong IDisposableオブジェクトがあり、 破棄する必要がない(または破棄すべきではない)BCLのもう1つの例です。それ。これは、BCLであっても、一般的な規則にはいくつかの例外があることを覚えておくためです;)
Mariusz Pawelski

166

あなたは何もリークしません-少なくとも現在の実装では。

Disposeを呼び出しても、MemoryStreamによって使用されているメモリはこれ以上速くクリーンアップされません。呼び出し後にストリームが読み取り/書き込み呼び出しで実行可能になるのを停止します。

MemoryStreamから別の種類のストリームに移動したくないと確信している場合、Disposeを呼び出さなくても害はありません。ただし、別のストリームを使用するように変更したとしても、早い段階で簡単な方法を選択したので、見つけにくいバグに噛まれたくないので、これ一般的に良い習慣です。(その一方で、YAGNIの議論があります...)

とにかくそれを行うもう1つの理由は、新しい実装 Disposeで解放されるリソースを導入する可能性があるためです。


この場合、関数は「呼び出しパラメーターに応じて異なる解釈が可能なデータ」を提供するため、MemoryStreamを返します。そのため、バイト配列である可能性がありますが、他の理由により、MemoryStreamとして実行する方が簡単でした。したがって、これは別のStreamクラスにはなりません。
Coderer、2008年

その場合、私は一般原則だけでそれを処分しようとします-良い習慣を構築する-などですが、それがトリッキーになってもあまり心配しません。
Jon Skeet、

1
リソースをできるだけ早く解放することが本当に心配な場合は、「using」ブロックの直後に参照をnullにして、管理されていないリソース(存在する場合)がクリーンアップされ、オブジェクトがガベージコレクションの対象になるようにします。メソッドがすぐに戻る場合、おそらくそれほど大きな違いはありませんが、メソッドで他のメモリを要求するなどの処理を続けると、確かに違いが生じる可能性があります。
Triynko、

@Triynko真実ではない:詳細については、stackoverflow.com / questions / 574019 /… を参照してください。
ジョージストッカー

10
YAGNIの議論は両方の方法でとることができます-実装するものを処分しないことを決定したため IDisposableは通常のベストプラクティスに反する特別なケースであるため、YAGNIの下で本当に必要になるまで行うべきではないと主張することができます原理。
Jon Hanna

26

はいリークの定義方法と後でどの程度意味するかに応じてリークがあります...

リークによって「メモリは割り当てられたままで、使用できなくても使用できます」という意味であり、後者の場合は、disposeを呼び出した後であればいつでも意味します。そうすると、永続的ではありませんが、リークが発生する可能性があります(つまり、アプリケーションランタイムの寿命)。

MemoryStreamによって使用されているマネージメモリを解放するには、その参照を無効にすることにより、その参照を解除する必要があります。これにより、ガベージコレクションの対象となります。これに失敗すると、それを使用し終えた時点から、参照がスコープ外になるまで一時的なリークが発生します。その間、メモリは割り当てに使用できなくなります。

usingステートメントの利点(単にdisposeを呼び出すよりも)は、usingステートメントで参照を宣言できることです。usingステートメントが終了すると、disposeが呼び出されるだけでなく、参照がスコープ外になり、参照を事実上無効にし、オブジェクトをガベージコレクションの対象にすぐにできるようにします。

すぐに参照を解除できないことは、古典的な「永続的な」メモリリークではありませんが、同じ効果があります。たとえば、(disposeを呼び出した後でも)MemoryStreamへの参照を保持し、メソッドの少し下でさらにメモリを割り当てようとすると、まだ参照されているメモリストリームによって使用されているメモリは利用できません。破棄を呼び出してそれを使用し終わったとしても、参照を無効にするか、または範囲外になるまで、あなたに。


6
私はこの反応が大好きです。時々人々は使用の二重の義務を忘れます:熱心な資源再利用熱心な逆参照。
キット

1
確かに、Javaとは異なり、C#コンパイラは「最後に可能な使用」を検出すると聞いたので、変数が最後の参照の後にスコープから外れる運命にある場合、最後に可能な使用の直後にガベージコレクションの対象になる可能性があります。 。実際に範囲外になる前。stackoverflow.com/questions/680550/explicit-nullingを
Triynko

2
ガベージコレクターとジッターはそのようには機能しません。スコープは言語の構成であり、ランタイムが従うものではありません。実際、ブロックが終了したときに.Dispose()への呼び出しを追加することにより、参照がメモリ内にある時間を長くしている可能性があります。ericlippert.com/2015/05/18/…を
Pablo Montilla

8

呼び出す.Dispose()(またはでラップするUsing)必要はありません。

呼び出す理由.Dispose()、リソースをできるだけ早く解放するためです。

たとえば、スタックオーバーフローサーバーについて考えてみましょう。このサーバーでは、メモリのセットが制限されており、何千ものリクエストが受信されます。スケジュールされたガベージコレクションを待たずに、できるだけ早くメモリを解放して利用できるようにします。新しい着信要求の場合。


24
ただし、MemoryStreamでDisposeを呼び出しても、メモリは解放されません。実際、Disposeを呼び出した後でも、MemoryStreamのデータを取得できます-試してみてください:)
Jon Skeet

12
-1これはMemoryStreamにも当てはまりますが、一般的なアドバイスとして、これは明らかに間違っています。破棄とは、ファイルハンドルやデータベース接続などのアンマネージリソースを解放することです。メモリはそのカテゴリに分類されません。ほとんどの場合、スケジュールされたガベージコレクションがメモリを解放するまで待機する必要があります。
ジョー

1
FileStreamオブジェクトの割り当てと破棄に1つのコーディングスタイルを採用し、MemoryStreamオブジェクトに別のコーディングスタイルを採用する利点は何ですか?
Robert Rossney、2009年

3
FileStreamには、実際にはDisposeを呼び出すとすぐに解放できるアンマネージリソースが含まれます。一方、MemoryStreamは、マネージバイトアレイを_buffer変数に格納します。この変数は、破棄時に解放されません。実際、参照をnullにするとメモリが破棄時にGCに適格になるため、MemoryStreamのDisposeメソッドでは_bufferもnullになりません。これはSHAMEFUL BUG IMOです。代わりに、残っている(ただし破棄された)MemoryStream参照は引き続きメモリに保持されます。したがって、いったん破棄すると、それがまだスコープ内にある場合はnullにする必要があります。
Triynko

@Triynko-「したがって、いったんそれを処分したら、それがまだスコープ内にある場合は、nullにする必要もあります」-同意しません。Disposeの呼び出し後に再度使用すると、NullReferenceExceptionが発生します。Disposeの後で再び使用しない場合は、nullにする必要はありません。GCは十分にスマートです。
ジョー

8

これはすでに回答されていますが、情報を隠すという古き良き時代の原則は、将来的にリファクタリングしたいことを意味します。

MemoryStream foo()
{    
    MemoryStream ms = new MemoryStream();    
    // write stuff to ms    
    return ms;
}

に:

Stream foo()
{    
   ...
}

これは、呼び出し側がどのような種類のStreamが返されるかを気にする必要がないことを強調し、内部実装を変更できるようにします(たとえば、ユニットテスト用にモックする場合)。

バーの実装でDisposeを使用していない場合は、問題が発生する可能性があります。

void bar()
{    
    using (Stream s = foo())
    {
        // do stuff with s
        return;
    }
}

5

すべてのストリームはIDisposableを実装します。メモリストリームをusingステートメントで囲みます。usingブロックは、ストリームが確実に閉じられ、どのような場合でも破棄されるようにします。

Fooを呼び出す場所はどこでも(MemoryStream ms = foo())を使用して行うことができます。


1
私がこの習慣で遭遇した1つの問題は、ストリームが他の場所で使用されていないことを確認する必要があることです。たとえば、MemoryStreamを指すJpegBitmapDecoderを作成し、Frames [0]を返しました(データを独自の内部ストアにコピーすると考えます)が、ビットマップは20%の時間しか表示されないことがわかりました。メモリストリームを破棄していました。
devios1 2010

メモリストリームを永続化する必要がある場合(つまり、usingブロックが意味をなさない場合)、Disposeを呼び出して、変数をすぐにnullに設定する必要があります。破棄すると、もう使用されることはないので、すぐにnullに設定する必要もあります。chaiguyが説明しているのは、リソース管理の問題のように聞こえます。それは、渡す対象が廃棄の責任を負う場合を除き、参照を渡すことはできません。そうする。
Triynko、

2

メモリをリークすることはありませんが、コードレビュー担当者はストリームを閉じる必要があることを示しています。そうするのは礼儀正しい。

メモリをリークする可能性がある唯一の状況は、誤ってストリームへの参照を残し、決して閉じない場合です。あなたはまだ本当にメモリリークされていませんが、あなたはしている不あなたがそれを使用すると主張する時間を延長します。


1
>まだメモリをリークしていませんが、使用していると主張する時間を不必要に延長しています。本気ですか?Disposeはメモリを解放せず、関数の後半でそれを呼び出すと、実際には収集できない時間を延長する可能性があります。
ジョナサンアレン

2
ええ、ジョナサンはポイントを持っています。Disposeの呼び出しを関数の後半に配置すると、実際には、コンパイラーがストリームインスタンスにアクセスする(閉じるために)必要があると、関数の非常に遅い段階で判断する可能性があります。これは、コンパイラーが関数の初期に最適なリリースポイント(別名 "最後の可能な使用のポイント")を計算できるため、disposeをまったく呼び出さない(したがって、ストリーム変数への関数後半の参照を回避する)よりも悪い可能性があります。 。
Triynko、

2

私は中のMemoryStreamを包む推薦bar()using主に一貫性を保つための文:

  • 現在、MemoryStreamはメモリを解放しません .Dispose()が、将来のある時点でれる可能性があります。または、あなた(またはあなたの会社の他の誰か)がそれを独自のカスタムMemoryStreamで置き換える可能性もあります。
  • プロジェクトにパターンを確立してすべてのストリームを確実に破棄するのに役立ちます。「一部のストリームを破棄する必要がありますが、特定のストリームは破棄する必要はありません」ではなく、「すべてのストリームを破棄する必要がある」と言うことで、より明確に線が引かれます。 ...
  • 他のタイプのストリームを返すことができるようにコードを変更した場合は、とにかく破棄するようにコードを変更する必要があります。

私が通常次のような場合に行う別のこと foo()IDisposableを作成して返す、は、オブジェクトの構築との間の障害がreturn例外によってキャッチされ、オブジェクトが破棄され、例外が再度スローされるようにすることです。

MemoryStream x = new MemoryStream();
try
{
    // ... other code goes here ...
    return x;
}
catch
{
    // "other code" failed, dispose the stream before throwing out the Exception
    x.Dispose();
    throw;
}

1

オブジェクトがIDisposableを実装している場合は、完了時に.Disposeメソッドを呼び出す必要があります。

一部のオブジェクトでは、DisposeはCloseと同じことを意味し、その逆も同様です。その場合、どちらかが適切です。

さて、あなたの特定の質問については、いいえ、あなたはメモリをリークしません。


3
「マスト」はとても強い言葉です。ルールがあるときはいつでも、ルールを破った場合の結果を知っておく価値があります。MemoryStreamの場合、影響はほとんどありません。
Jon Skeet、

-1

私は.netの専門家ではありませんが、おそらくここでの問題はリソースではなく、ファイルハンドルであり、メモリではありません。ガベージコレクターは最終的にストリームを解放し、ハンドルを閉じると思いますが、コンテンツをディスクにフラッシュすることを確認するために、明示的に閉じることが常にベストプラクティスになると思います。


MemoryStreamはすべてインメモリです。ここにはファイルハンドルはありません。
Jon Skeet、

-2

アンマネージリソースの破棄は、ガベージコレクションされた言語では非決定的です。Disposeを明示的に呼び出しても、バッキングメモリが実際に解放されるタイミングを完全に制御することはできません。オブジェクトがスコープ外になると、Disposeは暗黙的に呼び出されます。これは、usingステートメントを終了した場合でも、従属メソッドから呼び出しスタックをポップアップした場合でも同様です。これはすべて言われていますが、オブジェクトは実際には管理対象リソース(ファイルなど)のラッパーである場合があります。このため、finallyステートメントを明示的に閉じるか、usingステートメントを使用することをお勧めします。乾杯


1
正確ではない。Disposeは、usingステートメントを終了するときに呼び出されます。オブジェクトがスコープ外になったときは、Disposeは呼び出されません。
アレクサンダーアブラモフ

-3

MemorySteramは、管理オブジェクトであるバイトの配列にすぎません。これを破棄するか閉じることを忘れても、ファイナライズの頭上以外に副作用はありません。
リフレクター内のMemoryStreamのconstuctorまたはflushメソッドを確認するだけで、優れたプラクティスに従うだけでなく、閉じるまたは破棄する必要がない理由が明らかになります。


6
-1:4年以上前の質問に回答を受け入れて投稿する場合は、役立つものにしてください。
Tieson T.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.