[VB.NET]Null許容型のDecimal?→Decimalの変換を極力シンプルに書く

Decimal?→Decimalの変換どうするのがいい？という話です。
せっかちな人は最後の「結論」だけ読みましょう。

# 背景
DBのとある金額フィールドpriceがNull許容になっていたとします。

それに対応するEntityクラスのpriceフィールドは、Decimal?型になります。

しかし、最終的に計算する段階では、Decimal?のままだと困るので、Nullなら0として扱いたいわけです。

「だったら最初からDBのフィールドをNull許容にしたりせず、0を設定するようにしろよ。そもそもその値がNullというのはどういう状態を表すんだ」

という声も聞こえてきそうですが、現実の運用では色々あるんです。

とにかく今は、Decimal?→Decimalの変換をしたいわけです。
しかし、そのまま代入しようとしても、コンパイルエラーになります（もちろん、Option Strict Onです）。

```vb:コンパイルエラー
Dim price As Decimal = rec.price
```

Option Strict Offにすればコンパイルが通るようになりますが、当然、rec.price.HasValueがFalseの時には実行時例外が発生しますので、なんの解決にもなっていないどころか害悪です。

それでは、と、下のように書くとコンパイルは通るようになりますが、rec.price.HasValueがFalseの時、rec.price.ValueがInvalidOperationExceptionが発生します。

```vb:実行時エラー
Dim price As Decimal = rec.price.Value
```
priceにNothingを設定すれば0として扱われる（VBではNothingは規定値を表す為）のだから、rec.price.HasValueがFalseならNotingを返してくれればいいのにとも思いますが、Null許容型はそう単純な話でもないのでしょう。

```vb:Nothingを設定すれば0になるのに…
Dim price As Decimal = Nothing
```

というわけで、まずはバカ正直にHasValueとValueを使って対応してみます。
というのも、VB.NETのNull許容型の公式ページには、HasValueとValueの説明しか載っていないからです。

https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/language-features/data-types/nullable-value-types

> null 許容値型の最も重要なメンバーは、HasValue プロパティと Value プロパティです。 null 許容値型の変数の場合、HasValue により、変数に定義済みの値が含まれているかどうかがわかります。 HasValue が True の場合は、Value から値を読み取ることができます。 HasValue と Value はどちらも ReadOnly プロパティであることに注意してください。

そうですか、ということで、こんなコードを書いてみます。

```vb
Dim price As Decimal

If rec.price.HasValue Then
	price = rec.price.Value
Else
	price = 0
End If
```

ちゃんと動作します。
まどろっこしすぎて泣けてきますが、上記のページでこの書き方を推奨している為、実際にこう書いている人は多いと思います。

まともなコーディングをしたいと心掛けている人は、三項演算子を使うでしょう。

```vb
Dim price As Decimal = If(rec.price.HasValue, rec.price.Value, 0)
```

ずいぶん見やすくなりました。でももっとシンプルに書けないのだろうかと思います。特に、rec.priceを2回も書かないといけないところなんて寒気がします。

C#ならこう書けるんですが、VB.NETにはこんなものはないのです。

```C#
decimal price = rec.price ?? 0
```

いや、でも待てよ？ If演算子は2項でも書けたはず。Null許容型を第一引数に与えてこんな風に書けるのでは？

```vb:OKでした
Dim price As Decimal = If(rec.price, 0)
```

これはよさそうな感じですね。

ただ、いちいち「0」と書かねばならないのは、ちょっと面倒な気もしてきます。まぁ意図が明確で良い気もするのですが。

CDec()とか使えないんでしょうか？ DecimalにNothingを代入すると0になるのですから、CDec()でそのまま行ける気もします。

```vb:実行時エラーになる
Dim price As Decimal = CDec(rec.price)
```

しかし残念ながら、rec.price.HasValueがFalseの時、上記のコードはInvalidOperationExceptionを返します。

それでは、Convert.ToDecimal(value)はどうでしょうか。

```vb:rec.price.HasValueがFalseの時0が返る
Dim price As Decimal = Convert.ToDecimal(rec.price)
```

これは問題なく動きます。コードの意図も明確ですし、もうこれで良い気もしてきますが、Convert.ToDecimalはそもそも、文字列等の値を変換する汎用関数です。Nullの時に0として扱いたいだけの為に呼び出すのは、恐らくコスト的によくないでしょう。

いっそ、HasValueをチェックしてデフォルト値を返すNVLメソッドを、Nullable(Of T)に対する拡張メソッドとして作ってやろうか…と思い始めます。

と、ここまで来て、「まてまて、そもそもその程度の機能、標準で用意されていないのか？」と思い、Nullable(Of T)の詳細を見に行きます。

https://docs.microsoft.com/ja-jp/dotnet/api/system.nullable-1?view=net-5.0

|メソッド|説明|
|:--|:--|
|GetValueOrDefault()|現在の`Nullable<T>`オブジェクトの値、または基になる型の規定値を取得します|
|GetValueOrDefault(T)|現在の`Nullable<T>`オブジェクトの値、または指定した規定値を取得します|

…あるじゃないですか…。なんでこれ、最初の「Null許容型」の説明ページに書いてないんでしょうか。これまでの考察はなんだったのか。

というわけで…

# 結論

Decimal?→Decimalへの変換はGetValueOrDefault()メソッドを使います。

```vb
Dim price As Decimal = rec.price.GetValueOrDefault()
```

rec.priceがNullの時（実際にはNullというわけではなく、rec.price.HasValueがFalseになっている時）には、0が規定値として返ります。

もし -1 を規定値としたい場合には、次のように書きます。

```vb
Dim price As Decimal = rec.price.GetValueOrDefault(-1)
```

お好みで、こちらの書き方も良いと思います。

```vb
Dim price As Decimal = If(rec.price, 0)
```

ちなみに、Decimal型の話として書きましたが、もちろんInteger型など他のプリミティブ型でも使える話です。

「Nullの時に規定値を取得っていうけど、規定値ってどこで決まってるの？」と思った人は、こちらを参照してみてください。数値型の規定値は、全て0です。C#の記事ですが、たぶんVB.NETでも同じでしょう。

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/default-values