|
2018/02/27(Tue) 23:13:34 編集(投稿者)
■No86682 (麺スキー さん) に返信 > IDisposableが実装されているにもかかわらず、万が一、このクラスの使い手側がDispose()を忘れてしまった場合、 > 最悪、ガベージコレクションが働いたときに、Dispose()メソッドに記載したファイル削除が実行されるのでしょうか?
されません。なのでそれを保証できない場合においては、 保険として Object.Finalize をオーバーライドせねばなりません。
言い方をかえると、アンマネージリソースを持ったクラスを作成するに当たり、 IDisposable を実装していなくクラスを Inherits する場合や、 Inherits 無しで(素の Object 型からの継承で)作る場合など、 IDisposable インターフェイスを直接 Implments する場合には、 Finalize からの Dispose 呼び出しを含める必要があります。
ただそのための実装は、暗黙的な実装ルールがありまして、 最近の Visual Studio では、自動的にそのテンプレートルールが 用意されるようになっています。
それが最初に提示頂いたコードで言うところの 『アンマネージ リソースを解放するコードがある場合にのみ、Finalize() をオーバーライドします。』 の部分に当たります。
このルールでは、IDisposable.Dispose を実装するのとは別に、 Boolean 型の引数を持った NotInheritable なクラスの場合は、Private Sub Dispose(Boolean) メソッド それ以外のクラスの場合は、Protected Sub Dispose(Boolean) メソッド が用意するのが通例です。
最初に御自身が提示されたコードのテンプレートもそうなっていましたし、 System.IO.FileStream クラスや System.ComponentModel.Component を 継承した場合も、Sub Dispose(Boolean) なメソッドがあるはずです。
この実装ルール自体は慣習的なものですが、この実装ルールに のっとったクラスでは、ベースクラスのファイナライザの中から Dispose が呼ばれるように設計されていますので、継承先で追加の ファイナライザの実装を用意する必要はありませんし、 実装すべきでもありません。
しかし、そのベースクラスそのものを実装するようなケースでは、 ファイナライザを実装しなければなりません。
とりあえず、FileStream を継承して実装するならこんな感じでしょうか。
Public Class TesClass Inherits System.IO.FileStream
Private _FilePath As String Public ReadOnly Property FilePath As String Get Return _FilePath End Get End Property
Public Sub New() Me.New(System.IO.Path.GetTempFileName()) End Sub Private Sub New(p As String) MyBase.New(p, IO.FileMode.Open) _FilePath = p End Sub
Protected Overrides Sub Dispose(disposing As Boolean) Try System.IO.File.Delete(FilePath) Catch Finally MyBase.Dispose(disposing) End Try End Sub
#Region "以下、Stream クラスのその他のオーバーライドが続く" #End Region End Class
> Public Class TesClass > Implements IDisposable
他のクラスから継承して作るのではなく、上記のように 直接 Implements IDisposable する場合にはファイナライザが必要です。 ただその場合は、NotInheritable として実装した方が良いかも知れません。
NotInheritable としない場合は、Component や FileStream のように、 Dispose 実装をオーバーライドできるようにしておかないと、 継承先で扱い難いクラスになってしまうでしょう。
詳しい話は、先に紹介した URL を参照していただければと思いますが、 手順をざっくり並べると、こんな感じになります。
(1) Protected Overridable Sub Dispose(disposing As Boolean) の中身を実装します。 「引数が True の時にアンマネージリソースを解放する処理」と 「引数が False の時にマネージリソースを解放する処理」が必要です。 Me.disposedValue は、Dispose が複数回呼ばれた場合に備えるための 処理済フラグとして利用してください。
(2) Protected Overrides Sub Finalize() のコメントは解除します。テンプレートの中身は修正しなくて OK です。 これが呼ばれるタイミングでは、マネージリソースは解放済みなので、 「Dispose(False)」では引数は False 固定で呼び出すことになります。 継承元のファイナライザを呼び出すための「MyBase.Finalize()」も必須です。
(3) Public Sub Dispose() Implements IDisposable.Dispose のテンプレートの中身も変更しなくて構いません。 明示的に Dispose された場合には、これを通じて「Dispose(True)」されますし、 Dispose し忘れでガベージコレクトされた場合は、(2) によって「Dispose(False)」されます。 また、Dispose(Boolean)が呼ばれた時点で、マネージリソースの解放は 終わっていますので、(2) が呼ばれないようにするために、 SuppressFinalize メソッドを呼び出して、ファイナライザが呼ばれないようにします。
繰り返しになりますが、上記は、自身で IDisposable を 実装する場合の手順です。
System.ComponentModel.Component クラスなどのように、 「IDisposable を実装したクラス」を継承する場合には、 ベースクラス側で (2) や (3) が実装済みであることが多いので、 ファイナライザの追加実装は不要となり、(1) のみの実装で十分です。
|