質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

5回答

3838閲覧

【C#】IDisposableインターフェイス

syogakusya

総合スコア67

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

1クリップ

投稿2016/09/20 04:47

##質問
以下のようなクラスが存在する場合、このクラスにはIDisposableインターフェイスを実装するべきですか?
皆さんの意見をお聞かせください。

C#

1class Sample 23 StreamWriter _st; 4 5 public Sample() 67 // _stの初期化処理 8 ... 910

##背景
MSDNのあるドキュメントには、直接的にアンマネージリソースを扱わない場合はIDisposableインターフェイスを実装するべきではないという記事がある一方、MSDNが提示するDisposeパターンにはマネージリソースの開放を行う場所があります。
他にも、マネージリソースのみを扱うクラスならDisposeパターンもデストラクタも使わずにDisposeメソッドを実装するだけでよいとする記事を書いている人もいます。(MSDNではないです)
IDisposableに関する様々な議論も見てはみたのですが、いまいちまとまったものがなかったので、自分で質問しながら考えを深めていこうと思いました。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答5

0

マネージリソースはとあるインスタンスとそれが内包するデータが完全にGCの対象になっているリソースで、それよりも外にある何らかのデータは全てアンマネージリソースだと思います。

ファイルやネットワーク、ビットマップ、ウィンドウなどはOSにある機能なのでこれらを示すオブジェクトはすべてアンマネージリソースで、これらをラップするクラスはIDisposableの実装が必要です(File,Socket,Image,Control等々)
また、StreamWriterのように直接アンマネージリソースを保持しませんがStreamを内包しており、これを破棄するためにIDisposableを実装するものもあります。
ですので、アンマネージリソースを直接保持する、またはIDisposableを実装するオブジェクトを保持するクラスはIDisposableを実装すべきで、Sampleクラスのような場合も必要だと思います。

あと、マネージリソースのみでも大量にメモリを使用する場合など使用者側に明示的な破棄のタイミングを提供したほうがいい場合IDisposableを実装したほうがいいと思います(MemoryStreamなど)

投稿2016/09/20 06:10

toki_td

総合スコア2850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

こんにちは。
自分なら、このクラスにはIDisposableを実装します。
フィールドにIDisposableインターフェースを持つインスタンスを設置する場合は必ずIDisposableを実装し、実装するDisposeメソッド内で全てのIDisposableなメンバー変数のDisposeメソッドを呼ぶように設計します。

Disposeパターンについては、クラス内に直接アンマネージリソースを設置する場合以外は実装する必要はないと思います。Disposeパターンは、万が一ユーザーがリソースの解放記述を行わなかった場合でもリークを回避するためのパターンです。

質問のコードの場合は、マネージリソースであるStreamWriterのみを持っているため、通常のIDisposableの実装で十分です。逆に、何も実装しないのは非常にまずいです。ユーザーがIDisposableであるStreamWriterをDisposeする手段がなくなるからです。

投稿2016/09/20 05:29

tamoto

総合スコア4103

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

syogakusya

2016/09/20 10:59

ご回答ありがとうございます。 実際にはSampleクラスを保持するクラス、さらにそのクラスを保持するクラスなどがあり、自分で作ったコードのほとんどのクラスがIDisposableインターフェイスを実装している状態です。 また、そのうちのほとんどのクラスの寿命がアプリケーション終了時のため、アンマネージリソースを直接扱うクラスがDisposeパターンを実装していて、かついつアンマネージリソースが開放されるかが重要でないなら、Disposeを呼ぶ意味もないような気がしています。 そういった場合でも、やはりこのようなクラスの全てにIDisposableインターフェイスを実装し、保持しているクラスをDisposeする処理を書いた方がいいでしょうか? 非同期関連のクラスばかりで、using文を使うことができるクラスは僅かです。
tamoto

2016/09/20 23:31

その場合だと、クラスの設計自体が間違っている可能性が高いです。 本来、リソースはフィールドには出来る限り置かないようにするべきです。まず「リソースを内包するクラスはそれ自体がリソースである」という認識を持ちましょう。例えば、「Sampleクラスそのものが一種のリソースである」と自信を持って言い切れない場合は、フィールドにリソースを設置するのは良くないです。 リソースはフィールドには保持せず、実際に必要になった時にメソッドの引数として渡し、使い終わった時に即座にDisposeするような設計にはならないでしょうか? もう一つの方法としては、「リソースを常に開きっぱなしにし、アプリケーションの終了をその寿命とする」設計の場合です。この場合は、リソースを設置したクラスをシングルトンにしてしまい、永久にリソースを保持し続ければ良いので、IDisposableを実装する必要はありません。良い設計ではないですが。 「非同期関連のクラスなのでusingが使えない」というのはちょっとよくわからないです。
syogakusya

2016/09/21 00:55

なるほど、おかしな書き方をしてしまっている可能性が高いのですね。 たとえばこの場合なら、コンストラクタでStreamWriterをもらうようにすればよいのでしょうか?
tamoto

2016/09/21 01:52

いえ、その部分の違いはあまり関係なく、Sampleクラスの「フィールドにStreamWriterを配置している」という部分が重要です。 メンバ変数の使い方にもよりますが、一般的にクラス内にリソースを内包するということは、そのクラス自体もリソースであると「見なす」のが適切です。もし、Sampleクラス自体がリソースであるとは言えず、「リソースを利用して何かをする」立場であれば、その処理の起点となるメソッドに引数として外からリソースを引き渡すようにしたほうが良いです。この場合はリソースをメンバ変数に格納する必要がなくなります。 リソースというものは基本的には「使う時に開いて」、「使い終わったら即座に閉じる」ようにするものです。 もし、プログラム内で一つのSampleクラスを持ち回して、その間StreamWriterを使い続けているのであれば、それはSampleクラスが実質リソースであると言えるので、SampleクラスにIDisposableを実装し、通常のマネージリソースと同等に扱えばいいでしょう。その場合は、「使う時にSampleクラスをnewし」、「使い終わったら即座にDispose」となるはずです。
syogakusya

2016/09/21 04:06 編集

なるほど、ありがとうございます。 そのあたりの考え方は大丈夫そうです。 私のつくったコードは、データベースからとってきたデータをファイルに書き続けるコードで、そのスレッドを扱うクラス、データベースからデータをとってくるクラス、ファイルに書きこむクラスに分かれているのですが、データをファイルに書く部分がSampleクラスの仕事でStreamWriterのラッパークラスのような形で実装されています。 これを「リソースを利用して何かをするクラス」と考えることもできるのですね。 その場合、スレッドを扱うクラス側にこのStreamWriterをおくべきでしょうか。 追記: もし私のコードが異常で、クラスの構成について全く検討もつかないようでしたらおっしゃってください。 帰宅次第コードを追記させていただきます。 現在あるクラスは以下です。(業務に関わるので具体的に書けず申し訳ありません。) 1.スレッド全部を扱うクラス「AllThread」 (Disposeで各スレッド内包クラスのDisposeを呼び出す) 2.前述の、データベース・ファイル処理スレッドを扱うクラス「ThreadA」 (Disposeで3と4のDisposeを呼び出し、自分のスレッドも終わらせる) 3.データベースからとってくるラッパーククラス「SampleGet」 (Disposeでコネクションを閉じる) 4.ファイルに書くラッパークラス「Sample」 (DisposeでWriterを閉じる) 5.特定のサーバーから特殊な通信でデータをとってきてそれをXMLに書く処理をするスレッドを扱うクラス「ThreadB」 (Disposeで6のDisposeを呼び出し、自分のスレッドも終わらせる) 6.特殊な通信を行うdllを扱ってデータをとってくるラッパークラス「SampleGet2」(Disposeで提供されたライブラリ内クラスのDisposeを呼び出す) 7.特殊な通信でとってきたデータをXMLに書くクラス「Sample2」 (このクラスは都度XMLを開いているのでDisposeはしない) 上記のようにIDisposableインターフェースを実装しているクラスが多い状態を指して、何かおかしいんじゃないかと思っています。
tamoto

2016/09/21 04:55 編集

1つのStreamWriterを開きっぱなしにする設計とするなら、現状のまま、「Sampleクラスを一つのリソースとして扱う」方針でも良いと思います。 その用途の場合、自分なら、Sampleクラスにリソースは持たせず、「データをファイルに書く」メソッドを実行する度に、関数内ローカルでStreamWriterをusingで開き、与えられたデータを書き込み後即座にクローズする設計にしますね。開きっぱなしのリソースは扱いが面倒なのです。 追記 追記読み逃してました。 各クラスがリソースのラッパーであるなら、それもまたリソースであることは明らかです。なので、それぞれにIDisposableが実装されていることはおかしいことではありません。 ただ、そのリソースの取り扱いについて少し疑問があります。 クラス設計で「リソースをクラスに内包する」ということは、(一時的でも)開いた状態のリソースを「保持」する意図があることになります。このとき、「本当に開いた状態のリソースを保持する必要があるのか?」を考えてみてください。 SampleGetクラスを例にします。「データベースからデータを取り出す」ということは、「取り出すデータを決めて->DBコネクションを開き->データを取り出して->DBコネクションを閉じる」という動作になるはずです。この場合DBリソースは、「DBからデータを取り出すメソッドの内部でDBコネクションをusingで開いて使う」か、または「SampleGetクラス自体をDBリソースと見なして、(外から)SampleGetクラスをusingで開いて使う」の2種類になるはずです。前者の場合は、そもそもSampleGetクラスがリソースを保持しない設計となるので、SampleGetクラスはIDisposableではなくなります。 各クラスがリソースを「保持」する必要がなくなれば、IDisposableにする理由もなくなるので、IDisposable祭りにはならないはずです。 極論ですが、リソースは必ずusingで使うものであり、リソースをusing外で利用しなければならない状況は設計ミスだと考えてしまっても良いくらいです。クラス内に開いた状態のリソースを内包する設計なら、そのクラスはusingで使用される前提の設計になっているべきです。
syogakusya

2016/09/21 10:41

度重なるご回答ありがとうございます。 安心しました。これはおかしいことではなかったのですね。 なるほど、たしかに一度の処理内で必ずリソースを生成してDisposeするようにしてしまえば、そのクラスはリソースを保持しないクラスになりますね。 各クラスについて、リソースを保持しない設計も考えてみます。 その場合、リソースを持ち回す場合よりも処理が増えるので、それぞれどの程度のオーバーヘッドになるかが焦点になりそうです。 using文については、できるだけusing文を利用してDisposeを行おうとは思っているのですが、なかなか思うように使えません。 例えば、SampleGetクラスを保持するThreadAクラスにRunメソッドを実装し、メソッド内でSampleGetクラスのインスタンスを生成後にスレッド処理を開始した場合を考えると、スレッド開始直後にRunメソッドを抜けてしまうため、Runメソッド内でusing文を使ってSampleGetクラスを生成しても、スレッド処理中に利用したいSampleGetクラスのインスタンスがスレッド処理開始直後に解放されてしまうといった事態に陥ってしまうと思われます。 他にも、WinFormのフォームクラスに保持されている別スレッド処理を含むクラスのインスタンスをDisposeしたい場合なども、using文を使う正しい方法がわかりません。 こういった場合についてusing文を使う方法はありますか。
tamoto

2016/09/21 12:36

リソースを使用する度に作成と解放を行うのは確かに多少のオーバーヘッドになっているでしょう。しかし、そのクラスが開きっぱなしのリソースを保持していることにちゃんと明快な理由が付けられないのであれば、単なるパフォーマンスの問題のみでその設計にするのは間違いです。それくらいにリソースはデリケートに取り扱っていいものです。リソースのラッパーを使う場合は、コンストラクタのコーディングに気をつければそうコストが嵩むということもないはずです。 using構文について。その例では「SampleGetクラスを保持するThreadAクラス」という部分が重要ですね。保持する==開きっぱなしのリソース(SampleGet)を持ち続ける、という意味になります。ということは、これはそう簡単に閉じることは出来ないはずです。ThreadAクラスは明らかに「リソースを利用する立場」であると読み取れます。となると、ThreadAはリソースを内包するべきではなく、実際にリソースを使用するメソッドの内部でusingを使ってリソースを開くようにコーディングするべきです。 「リソースがスレッド処理開始直後に解放されてしまう」というのはちょっとよくわかりません。リソースを作成するのは「リソースを利用する直前」なので、この場面でリソースを作成するタイミングは「スレッドを作成し、それを開始する処理(Runメソッド?)」の中ではなく、「実際にスレッド内で処理される手続きの中(Taskクラスを使用しているのであれば、Task.Runの中など)」でリソースの作成と解放が行われるようにコーディングされるはずです。 using構文は言い換えると「リソースをここからここまでで使用する」という宣言だと見ることができます。ということは、usingで囲む行数はできる限り少なくしたほうが、意味合いとしてもメンテナンス性としても望ましいです。メソッド全体の処理を「リソースが既にあるもの」として考えず、「この部分でリソースを利用する」と意識して構築すると、usingの正しい使い方が見えてくるでしょう。
syogakusya

2016/09/22 10:51

色々と見えてきました。 様々な悪癖がついていたようです。 もう一度コードを見直してみようと思います。 ありがとうございました。
guest

0

MSDN ライブラリの以下の記事によると、"マネージリソースのみを使用する型は、ガベージコレクターによって自動的にクリアされるため、このような型で Dispose メソッドを実装しても、パフォーマンス上の利点はありません" とのことです。(current version の記事にはその説明はありませんが同じことかと思います)

Dispose メソッドの実装
https://msdn.microsoft.com/ja-jp/library/fs2xkftw(v=vs.100).aspx

しかし、実は、Dispose メソッドには、メモリ開放の機能以外に、GC.SuppressFinalize メソッド を実装することにより、冗長なファイナライザーの呼び出しを防ぐことができるという利点があるそうです。

GC.SuppressFinalize メソッド
https://msdn.microsoft.com/ja-jp/library/system.gc.suppressfinalize(v=vs.100).aspx

以下の記事の説明にも、"Dispose メソッドは、破棄するオブジェクトの SuppressFinalize メソッドを呼び出す必要があります。 SuppressFinalize を呼び出すと、オブジェクトが終了キューに置かれている場合は、そのオブジェクトの Finalize メソッドの呼び出しは行われません。Finalize メソッドの実行は、パフォーマンスに影響を与えることを覚えておいてください" と書かれています。

Dispose メソッドの実装
https://msdn.microsoft.com/ja-jp/library/fs2xkftw(v=vs.100).aspx

なので、上の記事のサンプルコードにあるように Dispose メソッドで GC.SuppressFinalize(this); を実行するようにすれば、冗長なファイナライザーの呼び出しを防ぐことができるということで、クラスに Dispose メソッドを実装することは意味がありそうです。

ただし、コンストラクタに GC.SuppressFinalize メソッドを実装できますので、冗長なファイナライザーの呼び出しを防ぐという意味では Dispose() メソッドを実装する必要はないケースもあります。

.NET のライブラリにもコンストラクタに GC.SuppressFinalize メソッドを実装しているものがあります。(例:SqlCommand クラス)

【追伸】

ひょっとして、質問者さんが書かれたサンプルコードで StreamWriter をどう Dispose するかという議論をしているのですかね?

そうあれば、ユーザーが Sample クラスの Dispose を呼ばなくても済むように、Sample クラス内で StreamWriter を使い終わったら自動的に Dispose されるようコーディングすべきだと思います。

投稿2016/09/20 05:27

編集2016/09/20 05:54
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

syogakusya

2016/09/20 10:39

ご回答ありがとうございます。 FinalizeメソッドはIDisposableインターフェイスを実装することで暗黙的に実装されるのだと思っていたのですが…何か酷い誤解をしていますでしょうか。 SuppressFinalizeを呼ぶことでメモリが開放されないといったことが起きるのではと思っていたのですが… ファイナライザーとはFinalizeメソッドのことでしょうか。 質問の意図が不明瞭で申し訳ございません。 IDisposableインターフェイスを実装するクラスを保持するクラスには、同様にIDisposableインターフェイスを実装しなければならないかという質問です。 Writerの寿命はSampleクラスと同じです。
退会済みユーザー

退会済みユーザー

2016/09/20 14:05

> FinalizeメソッドはIDisposableインターフェイスを実装することで暗黙的に実装されるのだと思っていたのですが…何か酷い誤解をしていますでしょうか。SuppressFinalizeを呼ぶことでメモリが開放されないといったことが起きるのではと思っていたのですが… ファイナライザーとはFinalizeメソッドのことでしょうか。 紹介した記事に以下のように書いてあるのですが、それは上の疑問の答えになっていないですか? "SuppressFinalize を呼び出すと、オブジェクトが終了キューに置かれている場合は、そのオブジェクトの Finalize メソッドの呼び出しは行われません。 Finalize メソッドの実行は、パフォーマンスに影響を与えることを覚えておいてください。 Dispose メソッドがオブジェクトのクリーンアップを完了していれば、ガベージ コレクターがそのオブジェクトの Finalize メソッドを呼び出す必要はなくなります" > IDisposableインターフェイスを実装するクラスを保持するクラスには、同様にIDisposableインターフェイスを実装しなければならないかという質問です。 そんなことはないと思いますが、 > Writerの寿命はSampleクラスと同じです。 ということであれば(何故そのような必要があるかは分かりませんが)、そうする必要があるのなら Sample クラスを Dispose するときに同時に StreamWriter も Dispose するということにせざるを得ないかと。
guest

0

掲載のコードの StreamWriter _st の寿命をSampleクラスと同じにしたい場合には IDisposable を実装するのが吉です。

マネージリソースのみを扱うクラスなら Disposeパターン もデストラクタも使わずに Disposeメソッド を実装するだけでよい

その通りでしょうけど、Disposeパターンの実装はIDEがサポートしてくれますから、実際に書くコードはDisposeパターンのほうが少ないです。

投稿2016/09/20 09:12

編集2016/09/20 09:17
hihijiji

総合スコア4150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nakit

2016/09/20 14:31

参考までに聞きたいのですが、最近のVisual StudioではDisposeパターンの実装までサポートしてくれるのでしょうか。※私が無知なだけで昔からあるのでしょうか。 自分は独自のコードスニペットを用意しているため実装に手間がかからないのは同意なのですが、標準の機能があるなら知りたいです。
hihijiji

2016/09/21 01:57

Visual Studio 2015 からの機能みたいですね。 もっと前から使えてた気がしましたが、今 Visual Studio 2013 を開いたら出てきませんでした。 それと訂正します。 × StreamWriter _st の寿命をSampleクラスと同じにしたい場合 ○ StreamWriter _st の寿命をSampleクラスのインスタンスと同じにしたい場合
nakit

2016/09/21 12:47

Visual Studio 2015 で確認できました。情報ありがとうございました。
guest

0

使い方によると思いますが
Sample内でusingを使いdisposeするので「しない」かな

投稿2016/09/20 05:42

編集2016/09/20 06:07
dn315

総合スコア200

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問