C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[4]: IDisposableインタフェースの応用について


(過去ログ 148 を表示中)

[トピック内 5 記事 (1 - 5 表示)]  << 0 >>

■86673 / inTopicNo.1)  IDisposableインタフェースの応用について
  
□投稿者/ 麺スキー (1回)-(2018/02/27(Tue) 12:30:28)

分類:[VB.NET/VB2005 以降] 

こんにちは。VB2013で社内の業務用ツールを作成しています。

現在作成中の社内ツールでは、アプリ内部で呼び出すサブツール用のデータを
Path.GetTempPathで得られる一時フォルダ内に作成し、
サブツールの作業が終わったら一時フォルダ内のデータを削除しています。
(実際にはPath.GetTempFileNameを使用)

この一時ファイル作成管理を汎用的に使いたいと思い、
一時ファイル作成管理機能をクラス化したいと思いました。

色々と調べた所、IDisposableインタフェースというものを実装すると
Dispose()で一時ファイルを削除したり、Usingが使えたりするので
これを使ってみようと思いました。
VSで、クラス名の所に「Implements IDisposable」を記述すると、
実装すべきコードが自動的に生成されましたが、
・マネージ オブジェクト
・アンマネージ リソース (アンマネージ オブジェクト) 
のキーワードがイマイチ理解できていません。

ここでいう、「マネージ オブジェクト」とは何でしょうか?
例えばBitmapのような、.Netが用意したオブジェクトでしょうか?
また、「アンマネージ リソース (アンマネージ オブジェクト)」が何を指すか
理解していないのですが、今回の「一時ファイルの削除」は、
「アンマネージ リソースの解放」の部分で行えばよいのでしょうか?
その場合、Finalize()のオーバーライドが必要でしょうか?

Public Class TesClass
    Implements IDisposable

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 重複する呼び出しを検出するには

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: マネージ状態を破棄します (マネージ オブジェクト)。
            End If

            ' TODO: アンマネージ リソース (アンマネージ オブジェクト) を解放し、下の Finalize() をオーバーライドします。
            ' TODO: 大きなフィールドを null に設定します。
        End If
        Me.disposedValue = True
    End Sub

    ' TODO: 上の Dispose(ByVal disposing As Boolean) にアンマネージ リソースを解放するコードがある場合にのみ、Finalize() をオーバーライドします。
    'Protected Overrides Sub Finalize()
    '    ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(ByVal disposing As Boolean) に記述します。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' このコードは、破棄可能なパターンを正しく実装できるように Visual Basic によって追加されました。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' このコードを変更しないでください。クリーンアップ コードを上の Dispose(disposing As Boolean) に記述します。
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class

引用返信 編集キー/
■86680 / inTopicNo.2)  Re[1]: IDisposableインタフェースの応用について
□投稿者/ 魔界の仮面弁士 (1580回)-(2018/02/27(Tue) 14:37:39)
2018/02/27(Tue) 16:02:29 編集(投稿者)

No86673 (麺スキー さん) に返信
> ここでいう、「マネージ オブジェクト」とは何でしょうか?

この場合のマネージ(managed) オブジェクトとは、
.NET(の自動メモリ管理)の範疇にあるオブジェクト、といったところです。

ネイティブコード(API などによって OS 機能を直接呼び出すようなコード)を
アンマネージコード、.NET によって実装されている部分をマネージコードと呼ぶこともあります。


> また、「アンマネージ リソース (アンマネージ オブジェクト)」が何を指すか

ファイルやネットワーク接続といった資源は、.NET ではなく OS で管理されるものなので、
基本的にはアンマネージなリソースに分類されます。

特に、Win32 API で管理される「オブジェクトのハンドル」などは、
unmanaged な資源の典型例と言えます(HWND とか HOBJECT とか HDC とか)。

具体例で言うと、Marshal.AllocHGlobal メソッドや Marshal.AllocCoTaskMem メソッドによって
確保されるメモリ資源などは、アンマネージなメモリリソースに当たりますね。
前者は kernel32.dll の LocalAlloc 関数、後者は ole32.dll の CoTaskMemAlloc に相当します。

Graphics.GetHdc メソッドなら、gdiplus.dll の GdipGetDC 関数への呼び出しですし、
Graphics.ReleaseHdc メソッドは gdiplus.dll の GdipReleaseDC 関数への呼び出しです。
この場合、GetHdc メソッドが返す IntPtr 値は、『GDI』で管理されるデバイスコンテキストという
オブジェクトへのハンドルであり、これはアンマネージリソースに分類されます。
それを呼び出すための System.Drawing.Graphics クラスは、『.NET』で管理されるマネージオブジェクトですが、
それが内部で保持している Graphics オブジェクトは、『GDI+』で管理される資源(GpGraphics)です。


> 例えばBitmapのような、.Netが用意したオブジェクトでしょうか?
上記の Graphics クラス同様、「アンマネージリソースを保持したマネージオブジェクト」ですかね。

たとえば System.IO.Stream を受け取る System.Drawing.Bitmap クラスのコンストラクタは、
『GDI+』のフラット API である GdipCreateBitmapFromStream 関数を呼び出していますし、
それによって生成されるのは、GDI+ の Graphics オブジェクトを指し示す GpGraphics 型の
アンマネージ オブジェクトです。また、System.Drawing.Bitmap のデストラクタや、
IDisposable.Dispose を通じて呼ばれる解放処理では、GDI+ の GdipDisposeImage 関数への
呼び出し(アンマネージコード)が含まれているといった具合です。


このほか、ActiveX コンポーネントも、.NET で管理されるものではなく
COM の世界で管理されるものなので、これも .NET から見るとアンマネージとなります。
(COM のオブジェクトの場合 COM の世界でのガベージコレクトがあるのですが、
 それらは .NET におけるガベージコレクトとは管理方法が異なるので)


> 理解していないのですが、今回の「一時ファイルの削除」は、
> 「アンマネージ リソースの解放」の部分で行えばよいのでしょうか?
> その場合、Finalize()のオーバーライドが必要でしょうか?
> Public Class TesClass
>   Implements IDisposable

殆どの場合、Finalize 部はコメントアウトしたままで十分なはずです。

Finalize を不用意に実装すると、寿命を延ばしてしまうことにも
繋がります。下記を参照してみてください。


[オブジェクトの破棄] - [IDisposableインターフェイス]
http://smdn.jp/programming/netfx/disposing/#IDisposable

[C# のメモリ管理] - [ファイナライズのコスト]
http://ufcpp.net/study/csharp/rm_gc.html?sec=cost-to-finalize#cost-to-finalize
引用返信 編集キー/
■86682 / inTopicNo.3)  Re[2]: IDisposableインタフェースの応用について
□投稿者/ 麺スキー (2回)-(2018/02/27(Tue) 16:03:26)
魔界の仮面弁士様、詳細な説明を頂き、誠にありがとうございました。
また、殆どの場合、Finalize部は不要とのこと、理解しました。

ところで、今回作成しようとしている一時ファイル管理クラスですが、
IDisposableが実装されているにもかかわらず、万が一、このクラスの使い手側がDispose()を忘れてしまった場合、
最悪、ガベージコレクションが働いたときに、Dispose()メソッドに記載したファイル削除が実行されるのでしょうか?
引用返信 編集キー/
■86685 / inTopicNo.4)  Re[3]: IDisposableインタフェースの応用について
□投稿者/ 魔界の仮面弁士 (1581回)-(2018/02/27(Tue) 17:04:16)
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) のみの実装で十分です。
引用返信 編集キー/
■86686 / inTopicNo.5)  Re[4]: IDisposableインタフェースの応用について
□投稿者/ 麺スキー (3回)-(2018/02/27(Tue) 18:42:19)
魔界の仮面弁士様、詳細な説明を頂き、誠にありがとうございました。
IDisposableが実装されているクラスを継承する場合、
自作クラスにImplements IDisposableを実装する場合、
よく理解できました。

これにて解決済みとさせて頂きます。
解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -