VB のたまご

作成日: 2015/12/03, 更新日: 2015/12/03


Nothing も DBNull も、とっつきにくいイメージを払拭しよう

参照型の方が分かりやすい Nothing

  •  皆様は普段、Nothing を使っているでしょうか?私は初期化する際に使っています。
  • Dim frm As Form = Nothing
    If frm Is Nothing Then
        Console.WriteLine("Nothing でした")
    Else
        Console.WriteLine("インスタンスがあります")
    End If
    
    ' 出力結果
    Nothing でした
    

  •  結果はご覧のとおり。変数に「Nothing」が入っているからですね。 本来は、参照先が入っていない(目印が無い状態だから)なのですが、まぁ、こう覚えても良いと思っています。 そういえば、Nothing を入れるのは、だいたい参照型の変数にしか使っていないことに最近気づきました。 Nothing は、値型には使えないのでしょうか?
  • Dim i1 As Integer = Nothing
    Dim s1 As String = Nothing
    Dim b1 As Boolean = Nothing
    Dim d1 As Date = Nothing
    
    If i1 = Nothing Then Console.WriteLine("i1 は、Nothing でした")
    If s1 = Nothing Then Console.WriteLine("s1 は、Nothing でした")
    If b1 = Nothing Then Console.WriteLine("b1 は、Nothing でした")
    If d1 = Nothing Then Console.WriteLine("d1 は、Nothing でした")
    
    ' 出力結果
    i1 は、Nothing でした
    s1 は、Nothing でした
    b1 は、Nothing でした
    d1 は、Nothing でした
    

  •  いえいえ、値型にだって使えます。って、あれ? Nothing って、さっきも言ったけど、 参照先が入っていない(目印が無い)状態じゃなかったっけ?値型にセットできたっけ? Nothing って、値じゃないんだけど。

スポンサーリンク


値型の場合の Nothing は、いつもの Nothing じゃないんだぜ

  •  もう少しだけ見てみましょう。
  • Dim i1 As Integer = 0
    Dim s1 As String = String.Empty
    Dim b1 As Boolean = False
    Dim d1 As Date = Date.MinValue
    
    If i1 = Nothing Then Console.WriteLine("i1 は、Nothing でした")
    If s1 = Nothing Then Console.WriteLine("s1 は、Nothing でした")
    If b1 = Nothing Then Console.WriteLine("b1 は、Nothing でした")
    If d1 = Nothing Then Console.WriteLine("d1 は、Nothing でした")
    
    ' 出力結果
    i1 は、Nothing でした
    s1 は、Nothing でした
    b1 は、Nothing でした
    d1 は、Nothing でした
    

  •  あれ、Visual Studio バグった?いえいえ、Visual Studio はバグっていません。正常です。 ということは、この結果からのこの判定だから、Nothing は、0 でもあり、空文字でもあり、False でもあり、 #12:00:00 AM# でもある、という新事実しか導き出せないですよね。つまり、Nothing は、参照型にセットしたときと、 値型にセットしたときで、動作が異なるという性質があるんですね。終わり。

  •  さて、これで終わってしまうと、私は皆さんに嘘を教えたことになります。 しかし、実際の動作は正しいです。これって、どういうことでしょう?

スポンサーリンク


なんだ、いつもの Nothing じゃん

  •  さぁ、このトリック、皆さんはお分かりでしょうか?
  • 実は、Nothing の仕事は、変数の「規定値」を返してあげることなのです。
  • Dim i2 As Integer
    Dim s2 As String
    Dim frm2 As Form
    
    If i2 = Nothing Then Console.WriteLine("i2 は、Nothing でした")
    If s2 = Nothing Then Console.WriteLine("s2 は、Nothing でした")
    If frm2 Is Nothing Then Console.WriteLine("frm2 は、Nothing でした")
    
    ' 出力結果
    i2 は、Nothing でした
    s2 は、Nothing でした
    frm2 は、Nothing でした
    

  •  はい、この通り。初期値をセットしていませんが、各変数には規定値が入っています。 規定値と、Nothing による規定値が一致するので、条件に合う判定結果となったんですね。 だから、例えば、Integer は 0 が規定値なので 0 と、 Nothing による規定値 0 が一致することで、 条件が一致していた、というカラクリでした。

  •  だったら、わざわざ初期値をセットしなくてもいいじゃんと思いますよね。 C# の場合は、未割当は不安定の元ということでコンパイルエラーになります。VB.NET の場合は、警告ですね。 よって、明示的に初期化するように推奨されています。

DB アプリで登場する DBNull

  •  データベースを扱うアプリケーションを作成する場合、UI 経由で、DB への追加、変更、削除等を行います。 例えば、データ取得を考える場合、DB からデータを取得して、プログラム内で編集することでしょう。 取得した DB データに、適切な値が入っていれば普通に使えるのですが、値が未登録の場合、DBNull という値が代わりに入っています。
  • Dim dt As New DataTable
    dt.Columns.AddRange(New DataColumn() {
                        New DataColumn("id", GetType(Integer)),
                        New DataColumn("age", GetType(Integer)),
                        New DataColumn("name", GetType(String))
                    })
    
    dt.Rows.Add(New Object() {1, DBNull.Value, DBNull.Value})
    dt.Rows.Add(New Object() {2, 24, "jiro"})
    
    Dim row As DataRow = dt.Rows(0)
    Dim id As Integer = CInt(row("age")) ' ← キャストエラー
    
    ' 例外エラー
    InvalidCastException はハンドルされませんでした。
    型 'DBNull' から型'Integer' への変換は無効です。
    

  •  Nothing の DB 版みたいなイメージでしょうか。同じなのであれば統一してくれればいいのに・・・と思いますが、 プログラム用の Null、DB 用の Null と、分けて考えるべきという仕様のようです。

  •  ※ Null は C# の場合のキーワードです。VB.NET では、Null は Nothing というキーワードです。 同じなのであればキーワードも統一してくれればいいのに・・・と思いますが、別物なので、キーワードも別物という仕様のようです。 ならば、DBNUll じゃなくて DBNothing にしてくれればいいのに・・・と思い、もういいや。
  • If Nothing Is DBNull.Value Then
        Console.WriteLine("Nothing と DBNull は同じです")
    Else
        Console.WriteLine("Nothing と DBNull は違います")
    End If
    
    ' 出力結果
    Nothing と DBNull は違います
    

  •  このように、Nothing と DBNull は別物扱いです。

Null 許容型変数について

  •  DBNull は、Object 型変数にしかセットできません。そういえば、DataRow は Object 型でしたね。 しかし、Object 型だと、どんな値もセットできてしまいます。それは困ります。ただし、作成するプログラムによっては、 値が未入力の場合は初期値ではなく DBNull のままにしたい場合があるので、 例えば、Integer なら、数字と DBNull のどちらかをセットできれば、もう少し扱いやすかったと思います。

  •  そこで、VB2008 からは、Null 許容型の変数を扱えるようになりました。Null 許容型とは、 任意の値型にも Nothing を扱うことができるようになる型のことです。
  • Dim i1 As Nullable(Of Integer) = Nothing
    If i1 Is Nothing Then Console.WriteLine("i1 は、Nothing でした")
    
    Dim i2 As Integer? = Nothing
    If i2 Is Nothing Then Console.WriteLine("i2 は、Nothing でした")
    
    ' 出力結果
    i1 は、Nothing でした
    i2 は、Nothing でした
    

  •  書き方は2通りあり、後者の?を使った方が楽かなという印象です。 ちなみに、この場合、Integer の規定値の 0 に変換されているのではなく、Nothing 自体がセットされています。 それでは次は、DBNull をセットしてみます。

  • Dim i1 As Nullable(Of Integer) = DBNull.Value
    Dim i2 As Integer? = DBNull.Value
    
    ' コーディングエラー
    型'System.DBNull' の値を、'Integer?' に変換できません。
    

  •  なんと、両方とも DBNull をセットできませんでした。あれ?間違ったかな?もう一回確認してみましょう。確認することは大事です。
  • Dim s1 As String = DBNull.Value    ' ← 型'System.DBNull' の値を、'String' に変換できません。
    Dim i1 As Integer = DBNull.Value   ' ← 型'System.DBNull' の値を、'Integer' に変換できません。
    Dim i2 As Integer? = DBNull.Value  ' ← 型'System.DBNull' の値を、'Integer?' に変換できません。
    Dim obj As Object = DBNull.Value
    

  •  さて、どうしましょう。終わりますか、このまま。見なかったことにして。

DataRow との組み合わせで、力を発揮する Null 許容型

  •  調べたところ、Null 許容型は、ツンデレ属性だということが分かりました。 今や、ツンデレ属性は、人類の間だけのものではありません。全てに平等です。プログラム言語しかりです。ワールドワイドなレベルの話ですね。 これからは、ツンデレ属性を取り込んだプログラム言語が流行ります。皆さん追いついてきてくださいね。

  •  どういうことかというと、DataRow からの値を受け取る場合に、不思議な力によって、Nothing だろうと、DBNull だろうと、 Nothing として渡す機能により、Null 許容型が効力を発揮する、というものです。
  • Dim dt As New DataTable
    dt.Columns.AddRange(New DataColumn() {
                        New DataColumn("id", GetType(Integer)),
                        New DataColumn("age", GetType(Integer)),
                        New DataColumn("name", GetType(String))
                    })
    
    dt.Rows.Add(New Object() {1, DBNull.Value, DBNull.Value})
    dt.Rows.Add(New Object() {2, 24, "jiro"})
    
    Dim row As DataRow = dt.Rows(0)
    Dim i1 As Integer? = row.Field(Of Integer?)("age")
    Dim s1 As String = row.Field(Of String)("name")
    

  •  同じく VS2008 から、DataRow の拡張メソッドとして、Field メソッドが追加されました。 このメソッドを使うと、任意の型にキャストする手間が無くなります。 それだけではなく、DBNull の場合、Nothing に変換して返してくれます。
  • If i1 Is Nothing Then Console.WriteLine("i1 は、Nothing でした")
    If s1 Is Nothing Then Console.WriteLine("s1 は、Nothing でした")
    
    ' 出力結果
    i1 は、Nothing でした
    s1 は、Nothing でした
    
    ' こういう風にも使えるよ
    If i1.HasValue Then
        Dim i3 As Integer = i1.Value
    End If
    
    Dim i4 As Integer = i1.GetValueOrDefault
    

  •  String 型は元々参照型なので、Nothing をセットできるので、 Null 許容型は不要です。このように、不思議な力というのは、 Field メソッドの事でした。これは本当にすごいことなのですが、今日の一番の収穫は、Null 許容型のツンデレっぷりでした。

  •  最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク