先日、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 <> 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 件のコメント:
コメントを投稿
注: コメントを投稿できるのは、このブログのメンバーだけです。