2009年11月29日
FxCopに学ぶ番外編(6) : IDisposable を正しく実装します
メールマガジン「C#プログラミングレッスン」では、今、[FxCopに学ぶ編] を連載中です。
そこで取上げようとしたルールで、下書きまでしたのですが、結局メルマガには書かないことにしたルールがいくつかあります。
そのまま捨ててしまうのはもったいないので、ブログに掲載します。
■IDisposableの実装(2)■
前回の続き
まず、なぜ、以下のクラスは、sealedを付加して継承できないようにしないと いけないのでしょうか?
前回のクラスを再度示します。
たとえば、Sampleを継承した、DelivedSampleを考えてみます。
このクラスにも、Dispoaseしないといけないフィールドが存在したとしましょ う。そうなると、必然的に、IDisposableインターフェースを実装しないとい けません。
しかし、このコードは、派生元のDisposeメソッドを隠すことになってしまい ます。
これだと、派生元のTextReaderオブジェクトを破棄することができませ ん。
つまり、Sampleクラスは、継承に対応していないクラスということになり ます。sealedを付けることで、表面上はこの問題を解決することができます。
無理やりコンパイルを通したとしても、以下のようなコードを書くと、
ポリモーフィズムが働きませんので、派生クラスDelivedSampleのDisposeメソ ッドが呼ばれず、DelivedSampleが保持しているリソースを破棄することがで きなくなってしまいます。
では、継承にも対応したクラスをお見せしましょう。
このコードの肝は、
- 複数回、Disposeが呼ばれても問題が発生しない。
- Finalaizeメソッド(デストラクタ)を実装することで、DIsposeを呼び出さなかった 場合でも、ガベージコレクション時にクリーンアップ処理をしている。
- Dispose を呼び出した場合は、GC.SuppressFinalize を呼び出し、 ファイナライズメソッドの呼び出しを抑制している。 (ファイナライズでやることは、すでにDisposeでやっているから不要ということ)
- 実際のクリーンアップ処理を受け持つ、virtualで引数ありのDisposeメソ ッドを用意している。これで継承に対応可能。
これは、Disposeパターンと呼ばれているものです。詳しくは、
http://msdn.microsoft.com/ja-jp/library/fs2xkftw.aspx
をご覧ください。
■FxCopのルール
CA1063: IDisposable を正しく実装します
Posted by gushwell at 23:47│Comments(7)│TrackBack(0)
この記事へのトラックバックURL
http://trackback.blogsys.jp/livedoor/gushwell/51931181
この記事へのコメント
最後の継承にも対応したクラス例に関してですが、
この例ではデストラクタを実装する必要はないのでは?
デストラクタが必要になるのはアンマネージリソースを
保持している場合だけと理解していたのですが・・・
たしかMSDNに「デストラクタは実装するだけで負荷に
なるから必要な場合だけにしてね」ってあったように思うので
この場合は実装しない方が例として適切な気がするのですが・・・
この辺りのMSDNの記事も散らばっていてきちんと理解しているか
不安なので、もし間違ったことを言っていたら指摘してください。
この例ではデストラクタを実装する必要はないのでは?
デストラクタが必要になるのはアンマネージリソースを
保持している場合だけと理解していたのですが・・・
たしかMSDNに「デストラクタは実装するだけで負荷に
なるから必要な場合だけにしてね」ってあったように思うので
この場合は実装しない方が例として適切な気がするのですが・・・
この辺りのMSDNの記事も散らばっていてきちんと理解しているか
不安なので、もし間違ったことを言っていたら指摘してください。
Posted by satoshi at
2009年12月04日 01:37
たしかに、そうですね。
この例では、デストラクタの中で、Dispose(false); としても、結局何もやっていないですしね。
後で訂正しておきます。
この例では、デストラクタの中で、Dispose(false); としても、結局何もやっていないですしね。
後で訂正しておきます。
Posted by Gushwell at
2009年12月04日 09:00
やはり、
~DisposableType() {
Dispose(false);
}
はあったうが無難だと思われます。
もし、このクラスが継承され、そこで、アンマネージドなリソースを利用していた場合、このデストラクタがあれば、安心です。
派生クラスで、デストラクタを書き忘れていても、これがあれば、アンマネージドリソースを解放することがでできます。
まあ、アンマネージドリソースを使う場面ってほとんどないので、実質的には無くても問題ないと思いますが...
~DisposableType() {
Dispose(false);
}
はあったうが無難だと思われます。
もし、このクラスが継承され、そこで、アンマネージドなリソースを利用していた場合、このデストラクタがあれば、安心です。
派生クラスで、デストラクタを書き忘れていても、これがあれば、アンマネージドリソースを解放することがでできます。
まあ、アンマネージドリソースを使う場面ってほとんどないので、実質的には無くても問題ないと思いますが...
Posted by Gushwell at
2009年12月05日 10:09
何度読み直してもわからないのですが、
~DisposableType() {
Dispose(false);
}
は
~DisposableType() {
Dispose(true);
}
でなければいけないのではないのでしょうか? Disposeし忘れたままファイナライズに入ったとき、Dispose(false)ではreaderが解放されません。
~DisposableType() {
Dispose(false);
}
は
~DisposableType() {
Dispose(true);
}
でなければいけないのではないのでしょうか? Disposeし忘れたままファイナライズに入ったとき、Dispose(false)ではreaderが解放されません。
Posted by
Sampo at
2010年06月03日 14:01
.NET Framework は、ガベージコレクションの機能が働いていますので、
Dispose(true)にしてしまうと、マネージリソースを再度破棄しようとする
のでよくないのではと思います。
そのため、ファイナライザで破棄するのは、ガベージコレクターが破棄して
くれないアンマネージリソースに限定します。
というのが僕の理解です。
Dispose(true)にしてしまうと、マネージリソースを再度破棄しようとする
のでよくないのではと思います。
そのため、ファイナライザで破棄するのは、ガベージコレクターが破棄して
くれないアンマネージリソースに限定します。
というのが僕の理解です。
Posted by Gushwell at
2010年06月03日 23:22
最初のコードを読んで思ったのですが、継承元のクラスのDisposeを、継承したクラスが隠蔽する場合って new キーワードの記述が警告メッセージとして表示されると思うのです。
それをあえて隠蔽するのは、継承クラス側の責任という考え方は間違っているのでしょうか?
また、Disposeメソッドを持つクラスを継承した場合に、基本クラスのアンマネージリソースを解放するロジックを考えるのは、継承クラス側のやるべきことのように感じました。
それをあえて隠蔽するのは、継承クラス側の責任という考え方は間違っているのでしょうか?
また、Disposeメソッドを持つクラスを継承した場合に、基本クラスのアンマネージリソースを解放するロジックを考えるのは、継承クラス側のやるべきことのように感じました。
Posted by
伊藤達也 at
2010年07月02日 17:09
基本は、「newキーワード付けたあなたの責任ですよ」ということで良いと
思うのですが、このDisposeメソッドの場合は、そう単純じゃありません。
例えば、
public new void Dispose() {
base.Dispose();
handle.Dispose();
}
とすれば解決するんじゃないかと考えるかもしれません、
そうなると、
Sample x = new DelivedSample();
x.Dispose();
というポリモーフィズムを使ったコードが、正しく動かなくなってしまいます。
結局、継承先のクラスでどんなコードを書こうが、Disposeを正しく行うコードを書くことができません。
つまり、「基本クラスのリソースを解放するロジックは継承クラス側」では書くことが出来ないのではと思います。
これは、継承元である Sampleクラスが悪いからです。
Sampleクラスが継承されることを考慮していないことになります。
継承を考慮していないクラスなんだから,
sealed 付ければ、表面上は解決しますよ、ということです。
でも、継承も考慮しないといけないクラスなんだとすると、sealedつけるわけにもいかないので、「Disposeパターン」の出番になります。
そして、このDisposeパターンを実装したクラスを継承する場合は、
7. Dispose メソッドから基本クラスの破棄を呼び出します
に話が続く、ということになります。
思うのですが、このDisposeメソッドの場合は、そう単純じゃありません。
例えば、
public new void Dispose() {
base.Dispose();
handle.Dispose();
}
とすれば解決するんじゃないかと考えるかもしれません、
そうなると、
Sample x = new DelivedSample();
x.Dispose();
というポリモーフィズムを使ったコードが、正しく動かなくなってしまいます。
結局、継承先のクラスでどんなコードを書こうが、Disposeを正しく行うコードを書くことができません。
つまり、「基本クラスのリソースを解放するロジックは継承クラス側」では書くことが出来ないのではと思います。
これは、継承元である Sampleクラスが悪いからです。
Sampleクラスが継承されることを考慮していないことになります。
継承を考慮していないクラスなんだから,
sealed 付ければ、表面上は解決しますよ、ということです。
でも、継承も考慮しないといけないクラスなんだとすると、sealedつけるわけにもいかないので、「Disposeパターン」の出番になります。
そして、このDisposeパターンを実装したクラスを継承する場合は、
7. Dispose メソッドから基本クラスの破棄を呼び出します
に話が続く、ということになります。
Posted by Gushwell at
2010年07月02日 21:29
