ページ

2007年8月10日金曜日

Re: VB : "Is Nothing" vs "= Nothing" と Nullable Type の強化

Shizuku Blog さんの 「VB : "Is Nothing" vs "= Nothing" と Nullable Type の強化」 を読んでちょっとおもしろそうだったので試してみました。
VB9 の LINQ では "Is Nothing" と書いた場合と "= Nothing" と書いた場合とで同じように振舞う、とのことですが、何がどうなっているのでしょうか?


以下、Visual Studio 2008 beta 2 で試してます。

まずはもっともシンプルそうなコードで。

    Dim ary As Integer?() = {1, 2, Nothing}
    Dim query = From x In ary _
                Where x = Nothing _
                Select x
    Console.WriteLine(query.Count())

これを実行してみると、、、あれ?ちゃんと "0" と表示されます。IL を見ると Where x = 0 と書いたのと同じような感じに解釈されているみたいです。
もちろん、Where x Is Nothing と書いた場合は意図どおり "1" という結果が表示されます。
どうやらここまで単純な場合だと "Is Nothing" と書いてやらないとダメなようです。

では、もうちょっと複雑なコード

    Dim ary() = { _
                    New With {.Name = "a", .Age = New Integer?(20)}, _
                    New With {.Name = "b", .Age = New Integer?(Nothing)} _
                }
    Dim query = From x In ary _
                Where x.Age = Nothing _
                Select x
    Console.WriteLine(query.Count())

おぉ、確かに "1" という結果が表示されます。この場合は "= Nothing" と書いても "Is Nothing" と同じように評価されているようです。
IL を見てみると "= Nothing" 部分は比較するコードは生成されておらず、代わりに Microsoft.VisualBasic.CompilerServices.Operators::ConditionalCompareObjectEqual() メソッドを呼び出すようになっています。この ConditionalCompareObjectEqual() メソッドの中までは調べていませんが、きっと Nullable と Nothing (null) との比較のときは HasValue をチェックするようになっているんでしょう。"= Nothing" と書いても "Is Nothing" と書いてもコンパイラは ConditionalCompareObjectEqual() メソッドを呼び出すコードを生成しています。なので、どちらの書き方をしても当然結果は同じになるわけです。


では、なぜ ConditionalCompareObjectEqual() メソッドが呼び出されるようになるんでしょうか?
それはきっと型が特定できないために late binding しているからでしょう。
ary の型を指定していないので object になっています。そして、そこから取り出した x も object です。Where の条件式 "x.Age = Nothing" の部分はラムダ式として解釈されているわけですが、そのメソッドの型を見ると bool Lambda(object) となっています。引数の object が x ですね。x が object なので x.Age というアクセスもできません (object には Age プロパティなんて無い)。そのため Microsoft.VisualBasic.CompilerServices.NewLateBinding::LateGet() メソッドを呼び出して Age にアクセスしています。そして ConditionalCompareObjectEqual() メソッドを呼び出して null と同じかどうかを判定しているわけです。
もちろん、こんなことになるのは Option Strict が Off の場合だけです。そもそも Option Strict On の場合はあちこちがコンパイルエラーになってしまいます。


では Option Strict On でも通るコード、すなわち late binding しないコードではどうなるでしょうか?


    Public Class Person
        Public Name As String
        Public Age As Nullable(Of Integer)
    End Class
 
    Sub Main()
        Dim ary As Person() = { _
                        New Person With {.Name = "a", .Age = 20}, _
                        New Person With {.Name = "b", .Age = Nothing} _
                    }
        Dim query = From x In ary _
                    Where x.Age = Nothing _
                    Select x
        Console.WriteLine(query.Count())
    End Sub

まぁ、答えは明らかなんですが、当然意図したようには動きません。この場合はきちんと "Is Nothing" としてやる必要があります。
(あれ?query の型は?と思ったらちゃんと IEnumerable(Of Person) と表示される。。。ひょっとして VB の Dim って C# の var の役割も兼ねてるの?)

1 件のコメント:

  1. Shizuku Blog の主の「けろ」です。


    試して頂き、ありがとうございます。


    >IEnumerable(Of Person) と表示される。。。ひょっとして VB の Dim って C# の var の役割も兼ねてるの?


    C#でいうvar は、VBでは、Dimでカバーしているので、Dim は、var と同じ動きをしているんだと思います。

    Dimの汎用性は、かなり謎なところが多いですね。

    返信削除