QA@IT

VB.NETのDataTableで勝手にインスタンスが破棄されることはあるのでしょうか?

8537 PV

VB.NETでRFIDのタグデータを収集するシステムを開発しました。
読み取ったタグデータをDataTableに追加し、読み取り時にSelectして重複読み取りしてないかチェックをしています。
画面上の指示でDataTableの内容をテキストに出力後、DataTable.Clearして再利用しています。
数か月問題なく動いていたのですが、ある時にDataTableをSelectするところで
「オブジェクト参照がオブジェクト インスタンスに設定されていません。」
とエラーメッセージが出ました。
DataTableのインスタンスはフォーム起動時に生成しており、フォームを閉じるまで破棄していません。
1日中稼働しているシステムなので、何らかの原因でframeworkが勝手に破棄してしまったという
ことはありうるのでしょうか?

OS:Windows 8.1
言語:VB.NET
  .Net framework4.5

回答

あくまで例えばの話ですが、以下のようなコードの場合に再現させることができます。
閉じる処理が走ったがそのまま続行してしまっているケースです。

' アプリケーションメニューのようなもの
' ボタンを一つ配置する
Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim fm2 = New Form2()
        fm2.Show()
    End Sub
End Class
' ボタンを一つ配置する
' こちらのボタンをクリックするとエラーが発生する。
Public Class Form2
    Private dt As DataTable
    Public Sub New()
        InitializeComponent()
        dt = New DataTable
        dt.Columns.Add("TAGDAT")
    End Sub

    Private Sub Button1_Click(sender As Object, _
                              e As EventArgs) _
                              Handles Button1.Click
        Dim isError = True
        If isError Then
            ' Error判定で閉じたが、メソッドは抜けない
            Close()
        End If

        Dim dr() As DataRow
        Dim TagDAT = "someDat"
        Try
            dr = dt.Select("TAGDAT = '" & TagDAT & "'")
        Catch ex As Exception
            MessageBox.Show(ex.Message)
        End Try

    End Sub

    Private Sub Form2_FormClosed(sender As Object, _
                                 e As FormClosedEventArgs) _
                                 Handles MyBase.FormClosed
        dt = Nothing
    End Sub
End Class

読み取りに時間がかかるためにループの途中でDoEventsでWindowsイベントを優先させてしまっているときに
間違って閉じてしまった(明示的に閉じようとした時なら何か気づくと思いますので)場合なども起こり得ます。

こうなっていると言うわけではありませんが、
Closedでしか解放していなくても起こるケースは存在しますよという参考程度で。

DataSetやDataTableで問題が絶対に起きないとは言えませんので、
Framework側を疑うのであればその日のデータ量が多くなかったかやメモリ使用量を気にしてみるとなにかわかるかもしれません。

1日ぐらいなら起動させたままのシステムはたくさんあると思いますし、
普段問題なく動いているとのことですので、
操作面で変わったことがなかったか、想定していないルートはないか、無理やりでもどうすればClosedを通した後にdt.Selectを通過させることができるのかも考えてみるといいかもしれません。
dt.Selectで例外が起きていたとしても、結果的にそこで例外が発生しただけで実際の原因は別の個所にあるかもしれませんので。
(もう十分に調査はしているのだとは思いますが。)

安全を期すならば、Formの生存期間とDataTableの生存期間を合わせずに、
処理に必要なタイミングで毎回生成して、メソッド間は引数などで渡すほうが良いかと思います。
(生成コストの問題はあるかもしれません)


上記コードを試した環境を書き忘れていたので追記
環境: Windows 10 x64, VS2013, .NET 4.5.1, 32bit 優先

編集 履歴 (1)

解決を遅らせる1つの要因として、作った人が絶対に間違ってないよと思ってしまう事によるものがあります。Nothingになるはずがないではなくなってしまうのだからそのタイミングが特定出来ないのならSelectのタイミングでNothing判定してデータ取得しなおしというのも1つの手かと思います。

編集 履歴 (0)
  • 確かに思い込みが弊害に繋がる事はあるかもしれませんが...どうやっても再現できないので。データ再取得ですが、DBの検索結果を格納しているわけではなく、RFIDタグの読取内容なので再度読取らせる事は運用上出来ません。 -
  • 読取った内容をどこかに保存しておけば出来るはず。1日中動作しているシステムのメモリデータが外部にバックアップされていないのは運用上どうなのでしょう? -

Shuさん

ご回答ありがとうございます。
必要な情報が足りず申し訳ありません。

>インスタンスを保持する変数がどこに定義されているか?
・クラス内Privateで宣言しており、Formのロード時にインスタンス生成しています。

>DataTable.Clear ではなく DataTable.Rows.Clearを使わないのは何故か?
・結果的にテーブルの構造を残したままデータさえ消えればいいと考えていたので、
厳密な動作に着目していなかったのですが、何か違いがあるのでしょうか?

>Selectというのは具体的にどのような構文で行っているのか?
 ・以下のような構文で行っています。

 Dim DataRow() as DataRow
 try
  DataRow= DataTable.Select("TAGDAT = '" & TagDAT & "'")
  if DataRow.count > 0 then
   Msgbox("重複あり")
   Exit try 
  End if
 Catch ex As Exception
 End Try

 ※DataTable ⇒ タグデータを格納しているテーブル 列はTAGDATのみ
 ※TagDAT ⇒ 読み取ったタグデータ

以上、宜しくお願い致します。

編集 履歴 (0)
  • "ある時にDataTableをSelectするところで 「オブジェクト参照がオブジェクト インスタンスに設定されていません。」とエラーメッセージが出ました。" というのは具体的にどのように確認されたのでしょう? 上のコードですと例外は Catch されてなかったことになってしまうような気がするのですが・・・ -
  • 情報が抜けていました。
    Catch ex As Exception 内でex.message をmsgboxで表示しています。
    -
  • DataTable が null になるのか RaraRow が null になるのか、どっちですか? あと、DataRow.count って何ですか? -
  • 上の私のコメントで DaraRow ⇒ DataRow の間違いでした、すみません。 -
  • >DataTable が null になるのか RaraRow が null になるのか、どっちですか
     ・憶測でしかないですが、DataTableのインスタンスがないと言われていると思います。あれ以来エラーが発生していないので確認できませんが。
    -
  • >あと、DataRow.count って何ですか?
     ・DataTable.Selectで該当するDataRow オブジェクトの配列が取得できますので、配列数を取得しています。
    -
  • 「あれ以来エラーが発生していないので確認できませんが」ということは質問者さんの方でも問題を再現できないということですよね。そうすると自分がお役に立てることはなさそうです。すみませんが、他の方の回等をお待ちください。
    -
  • 「配列数を取得しています」とのことですが、配列の長さは普通 Length プロパティで取得するようです。質問者さんが使った Count は多分 Enumerable.Count 拡張メソッドだと思いますが、メソッドの大文字小文字は区別しないんでしたっけ? (VB.NET はよく知らないのでハズレだったらすみません) -
  • 色々と考察して頂いてありがとうございました。
    Countについてはデータテーブルの行数取得と混同して使用してました。
    一次元であれば結果は同じようですが、メソッドの大文字小文字は区別されますね。
    -
  • VB.NETでは大文字、小文字の区別はありません。 -
  • 提示された内容だけだと特定できませんので、もっと範囲を広げて確認してみてください。DataTable変数がNothingに設定されなければ当該エラーはその箇所では多分でないと思いますので、よく探してみてください。 -
  • DataTable変数のスコープでNothingする個所はフォームのFormClosedイベントだけです。フォームを閉じない限りDataTable変数は破棄されないと思っています。 -
  • 他の方も言うようにDataTableがNothingで無いならば、DataTable.SelectでNullReferenceExceptionというのは考えにくいですね。よくあるミスでは別の例外でCatchしたがCatchブロック内で別の例外が発生しているということもあります。
    -
  • 現状提示されてるコードがどの程度本物と同じか(省略している部分があるのかないのか)はわかりませんが、その範囲では特定は難しい様に思います。
    解決策ではないですが次回起きた時のためにex.messageだけでなく、stacktraceもどこかに出力するようにしたほうがよいと思います(簡単にはex.ToString()でも)。
    -
  • 提示したソースは変数名は仮ですが内容は本物そのものです。現状ではcatch内でex.ToString()をログファイルに出力するようにして再現待ち状態です。 -

インスタンスを保持する変数がどこに定義されているか?

DataTable.Clear ではなく DataTable.Rows.Clearを使わないのは何故か?

Selectというのは具体的にどのような構文で行っているのか?

など分からない部分があり、この辺が怪しい気がします。

編集 履歴 (0)
ウォッチ

この質問への回答やコメントをメールでお知らせします。