ページ

2014年12月8日月曜日

[.NET] VB って If a = a Then が成り立たないことがあるのか!

先日、VB のコードを書いていて初めて知って衝撃を受けました。

Dim i As Integer? = Nothing
If i <> 1 Then
    i = 1
End If

なんのためらいもなくこういうコードを書いてました。当然 i は 1 ではないので If 文の中に入って 1 が代入されるもんだと思ってました。もちろん C# ではそうなります。けど、VB は違うんですね。コンパイラがどういうことやってるのかとりあえず IL を見てみました。IL の内容をそのまま VB で書くと

Dim result As Boolean?
If Not i.HasValue Then
    result = False
Else
    Dim compare As Boolean? = Not i.Value = 1
    result = compare
End If
If result Then
    i = 1
End If

こんな感じでした。(最適化無しのデバッグビルドです。最適化すればもっと効率いい IL になるんじゃないかと思います)
まず i が Nothing でないかを HasValue でチェックして、Nothing でない場合だけ 1 と比較しています。判定結果をいったん Boolean?(Nullable<bool>)に代入してる理由はよくわかりません。普通に Boolean で事足りると思うんですがなんでわざわざ Boolean? なんでしょうね?

というわけで、VB では比較する二項のいずれかの型が Nullable であり、その値が Nothing のときは常に条件は成り立ちません。たとえ等値演算子の両項が Nothing であっても成り立ちません。実際に以下のコードを試してみると「i0 と i1 は違う」と表示されます。

Dim i0 As Integer? = Nothing
Dim i1 As Integer? = Nothing
If i0 = i1 Then
    Console.WriteLine("i0 と i1 は同じ")
Else
    Console.WriteLine("i0 と i1 は違う")
End If

さらに同じ変数どうしでもダメです。

Dim a As Integer? = Nothing
If a = a Then
    Console.WriteLine("a と a は同じ")
Else
    Console.WriteLine("a と a は違う")
End If

これでも「a と a は違う」と表示されます。

もちろん、このことはリファレンスに載っています。
null 許容値型 (Visual Basic)
ここの「Null 許容型の比較」にあるように Nullable を比較した結果は True、False、Nothing のいずれかになり、そして Nothing は True でも False でもありません。さらに、Nothing は = や <> で比較することはできません(結果は常に Nothing になるので)。Nothing を比較できるのは Is か IsNot 演算子だけです。

いやぁ、びっくりした。
参照型の場合は、If obj Is Nothing Then のように Is、IsNot 演算子を使う癖がついてます。Nullable は値型だけど「なるべく参照型っぽく振る舞うようになっている」と思えば Is、IsNot を使わないとダメっていう発想になるかもしれませんが、なんとなく「値型だから」という気がしてなんの疑問も持たずに =、<> を使っちゃってました。そうか、ダメなのか。まぁ、気づいてしまえば確かに参照型っぽくしようと思うとこういう仕様になるっていうのは納得できますが。けど、上の例のように同じ変数の比較でも Nothing だと成り立たないっていうのはちょっとどうなのかなぁ。
それにしても、今までたまたま VB で Nullable を使うコードを書くことがほとんど無かったのもあって、ほんとにまったく気づいてませんでした(C# では Nullable 使いまくってますが)。

ちなみに、このことに気づいたのは INotifyPropertyChanged を実装していた時です。C# でオーソドックスに書いた時と同じように

Public Property MyValue As Integer?
    Get
        Return Me._MyValue
    End Get
    Set(value As Integer?)
        If Me._MyValue &lt;&gt; value Then
            Me._MyValue = value
            RaisePropertyChanged("MyValue")
        End If
    End Set
End Property

こんな風に書いて意図したように動かなくて気づいたわけです。(Me._MyValue か value が Nothing だとうまく動かない)
ではどういう風に書けばいいんだろう?と考えてみましたが、どうやら、

Public Property MyValue As Integer?
    Get
        Return Me._MyValue
    End Get
    Set(value As Integer?)
        If Not Me._MyValue.Equals(value) Then
            Me._MyValue = value
            RaisePropertyChanged("MyValue")
        End If
    End Set
End Property

と書けばいいようです。
Nullable の場合、一見 null になっていても HasValue、GetValueOrDefault() といったプロパティ/メソッドは呼び出せます。HasValue 呼び出せなかったら値が null かどうかチェックできなくなってしまうのでそりゃそうですよね。そもそも、あくまで Nullable<T> の中身が null であることを表しているだけであって Nullable<T> 自体のインスタンスはあるわけですから呼び出せて当然です。そして同様に Equals() も呼び出せます。Equals() は C# での == と同じ結果を返してくれるのでこれでうまくいきます。
もちろん、参照型の場合に値が  null なのに Equals() とか呼び出すと NullReferenceException なので注意。

と、今さらのように知って衝撃を受けたので長々と書いてみました。(VB にはまだまだ気づいてないことがありそう)

0 件のコメント:

コメントを投稿