Visual Basic 中学校 掲示板 投稿内容
タグのない投稿を抽出 統計 RSS

Visual Basic 中学校 > 投稿一覧 >

DataTableのDisposeについて 解決済み

タグの編集...

投稿者 SSD   (社会人)   投稿日時 2022/6/13 15:42:09
フォームアプリケーションで、データベース(以下、DB)から取得したレコードを
DataGridViewに表示するようにしています。

1. DataAdapterでDBからレコード取得
2. DataTableにDataAdapterのレコードを入れる
3. DataGridViewのDataSorceに2のDataTableを指定する

という流れで表示しています。

DataTableはIDisposeが実装されています。
ですが上記1~3の処理を行うプロシージャ内でUsing句をDataTableに用いると、
DataGridViewには何も表示されません。

例えば以下のコードで
Dim dataTable As New DataTable
となっている部分を
Using dataTable As New DataTable ~ End Using
とするとDataGridViewに何も表示されません。
(以下のコードの状態のままであればレコードが表示されます)

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Using connection As New SqlConnection("ConnectionString...")
        connection.Open()
        Dim query As String
        query = "Query..."
        Using adapter As New SqlDataAdapter(query, connection)
            Dim dataTable As New DataTable
            adapter.Fill(dataTable)
            connection.Close()
            dataGridView.DataSource = dataTable
        End Using
    End Using
End Sub



DataSorceとしていたオブジェクトがDisposeされるので、
レコードが表示されないのは当然である、ということは理解できています。
DisposeやUsingを用いなければレコードが表示されます。
しかし、その場合はメモリリークが起きていないのか不安です。

プロシージャレベルのスコープではなく、
クラスレベルでDataTableのインスタンスを宣言し、
フォームのイベントなどを用いてDataTableのDisposeを正確に制御すべきなのでしょうか?
(フォームが閉じたらDisposeするなど)
それともDataTableはDisposeを明確に行わなくても問題はないのでしょうか?

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2022/6/13 17:14:16
> Using句をDataTableに用いると、

実コードを見てみないとわかりませんが、こういうイメージなのかな…?

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim table1 As DataTable = CreateSampleDataTable()
    Using table1
        DataGridView1.DataSource = table1
    End Using

    Using table2 As DataTable = CreateSampleDataTable()
        DataGridView2.DataSource = table2
    End Using
End Sub

Function CreateSampleDataTable() As DataTable
    '実際には SQL Server から取得したものが返される 
    Dim tbl As New DataTable()
    tbl.Columns.Add("C1")
    tbl.Rows.Add("1")
    tbl.Rows.Add("2")
    tbl.Rows.Add("3")
    tbl.Rows.Add("4")
    tbl.AcceptChanges()
    Return tbl
End Function



> DataGridViewには何も表示されません。
そもそも、他の場所(この場合は DataGridView)が使用している最中のオブジェクトを
Dispose するという行為が御法度なんですけれどね……?


解放済みのリソースにアクセスしようとした場合は、
クラス側は ObjectDisposedException の例外を投げても良い事になっています。

ただそれは、オブジェクトの設計者が、そのように実装することがあるというだけ。
Dispose による破棄対象ではないリソースへのアクセスは許可されますし、
そもそも処分後も ObjectDisposedException を返さないクラス実装もあります。


> DisposeやUsingを用いなければレコードが表示されます。
> しかし、その場合はメモリリークが起きていないのか不安です。
SqlConnection は Dispose が必要です。
しかし DataTable クラスはそうではありません。


DataTable は IDisposable を実装してはいるものの、Dispose で破棄せねばならないような
リソースは特に保持しておらず、Dispose の必要もないオブジェクトなのです。

それなら何故 IDisposable なのか…と言えば、DataTable のベースクラスである
MarshalByValueComponent が、IComponent を実装したクラスであるためです。
(そして DataTable は、MarshalByValueComponent の Dispose をオーバーライドしていません)


もう少し細かい話をすると:

本来、Dispose パターンで実装されたクラスでは、Dispose し忘れた際の最後の砦として
「ファイナライザ」から自身を Dispose する取り決めになっています。
※内部的には、Dispose(False) のオーバーロードを呼び出す形で実装することが多いです。

しかし、ファイナライザがあるオブジェクトというものは、他のオブジェクトよりも生存期間が
長くなりやすく、パフォーマンスに影響を与えてしまうというデメリットがあります。

そこで明示的に Dispose() が呼ばれた場合には、「もはやファイナライズは不要である」事を
伝えるため、そのクラスは GC.SuppressFinalize(Me) を呼ぶよう実装せねばなりません。
https://docs.microsoft.com/ja-jp/dotnet/standard/garbage-collection/implementing-dispose

ところが DataTable の場合、IDisposable とはいいつつも、そもそも Dispose されるべき
アンマネージ リソースを持ち合わせていません。すべてマネージリソースなので、
Dispose に頼る必要が無く、解放処理はすべて GC 任せで構わないのです。

そのため DataTable の場合、ファイナライザ ではなく、「コンストラクタ」の時点で
自分自身をいきなり GC.SuppressFinalize するという実装になっています。
Dispose し忘れた場合にも、Dispose を意図的に呼びだす必要が無いからです。
https://referencesource.microsoft.com/#System.Data/fx/src/data/System/Data/DataTable.cs,182


> それともDataTableはDisposeを明確に行わなくても問題はないのでしょうか?
はい。DataTalble の Dispose を呼ぶ必要はありません。呼んでも構いませんが、
DataTable の場合には Dispose を呼ぶことよりも、変数のスコープを最小限にしておいたり、
不要な参照を保持し続けないようにすることの方が重要です。


> フォームが閉じたらDisposeするなど
System.Windows.Forms.Timer などは、フォーム終了時に自動的に Dispose されるよう、
デザイナーコードの Sub InitializeComponent() において
  Me.components = New System.ComponentModel.Container()
  Me.Timer1 = New System.Windows.Forms.Timer(Me.components)
という実装が組み込まれるようになっています。

そして、フォームでオーバーライドされた Sub Dispose(ByVal disposing As Boolean) を通じて、
Me.components が保持していたコンポーネント…この場合は Timer …が処分される設計です。
※同様に、Me.Controls が保持していたコントロールも処分対象。

しかし、DataSet や DataTable は Me.components に登録されるようにはなっていません。
Dispose する必要が無いので、それでも困らないというわけで。

投稿者 SSD   (社会人)   投稿日時 2022/6/13 18:18:34
魔界の仮面弁士  様

Disposeしなくていいとのことですので、
メモリリークは気にせず済みました。
ありがとうございます。