9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

背景

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

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

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

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

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

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

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

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

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

実行時エラー
Dim price As Decimal = rec.price.Value

priceにNothingを設定すれば0として扱われる(VBではNothingは規定値を表す為)のだから、rec.price.HasValueがFalseならNotingを返してくれればいいのにとも思いますが、Null許容型はそう単純な話でもないのでしょう。

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

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

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

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

Dim price As Decimal

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

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

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

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

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

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

decimal price = rec.price ?? 0

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

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

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

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

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

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

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

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

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)の詳細を見に行きます。

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

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

というわけで…

結論

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

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

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

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

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

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

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

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

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

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?