[VB.NET] 型変換／キャストのまとめ ―どう使い分ければいいのか？

# 明示的な変換（キャスト）

## キャスト演算子

C# のキャスト演算子 `()` に相当するものは VB.NET にはない。

## [DirectCast 演算子](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/operators/directcast-operator)

- `Object` 型とほかの型との変換において、VBランタイムヘルパーを使用しない分、`CType` 関数より高速に動作する。
- 値型と `Object` 型の間の変換にはボックス化、ボックス化解除が使われる。異なる型へはボックス化解除できず、暗黙の型互換があっても `InvalidCastException` が発生する。
- 実行時の型が変更後の型と一致しないとキャストに失敗する。たとえば、`Integer` 型から `String` 型へは変換できない。
- C# のキャスト演算子 `()` と異なり、暗黙の型互換があっても変換できない。たとえば、`Integer` 型から `Long` 型への変換はコンパイルエラーとなる。

## [TryCast 演算子](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/operators/trycast-operator)

- 参照型のみ。
- C# の `as` 演算子に相当。IL の [isinst 命令](https://docs.microsoft.com/ja-jp/dotnet/api/system.reflection.emit.opcodes.isinst?view=netframework-4.8) にコンパイルされる。
- `TypeOf ... Is ...` で判定してから `DirectCast` するのは変換が二度生じて無駄である。`TryCast` してから `IsNot Nothing` 判定する方がパフォーマンスがよい。

```vb:改善前
If TypeOf obj Is T Then
    Dim casted = DirectCast(obj, T)
     :
End If
```
```vb:改善後
Dim casted = TryCast(obj, T)
If casted IsNot Nothing Then
     :
End If
```
### 余談

`IsNot Nothing` に関し、『[コーディング規則 - Visual Basic | Microsoft Docs](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/programming-guide/program-structure/coding-conventions#use-the-isnot-keyword)』に

> IsNot の代わりに Not...Is Nothing キーワードを使用します。

とありますが、<font color="red">誤訳ですのでご注意ください</font>。
わざわざ VB2005 で追加したわけだし、「`IsNot` 演算子の方が否定対象が明確でいいのになあ」と思い、念のため[英語ページ](https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/program-structure/coding-conventions) をあたったところ、

> Use the IsNot keyword instead of Not...Is Nothing.

と全く逆になっていました。

フィードバックすべきか、と思ったらクローズが1件あったので、開いてみると、@tfukumori さんが別の箇所（`Handles` と `AddHandler `）に関して同様の[指摘](https://github.com/dotnet/docs.ja-jp/issues/1674)をされていて、なんと、

> 現在、機械翻訳されたコンテンツに対する翻訳の問題へのコントリビューションはできません。

という回答で終わっていました。
こんな初歩的・根本的な誤りを放置されるとは…やはり機械翻訳は参考程度に見た方がいいですね。

## [CType 関数](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/functions/ctype-function)

- Object 型とほかの型との変換において、[Microsoft.VisualBasic.CompilerServices.Conversions クラス](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.visualbasic.compilerservices.conversions) のようなVBランタイムヘルパーを使用する分、`DirectCast` よりパフォーマンスは落ちる。
- コンパイラによってインライン展開されるため、VBランタイムヘルパーが使用されない場合のパフォーマンスはよい。
- 暗黙の型互換があれば変換できる。暗黙の型互換がない場合、たとえば `Integer` 型から `DateTime` 型への変換はコンパイルエラー、`Object` 型にボックス化された `Integer` 値から `DateTime` 型への変換は実行時に `InvalidCastException` となる。
- VB.NET の `CType` 関数と C# のキャスト演算子とでは、結果が異なる場合がある。たとえば `Single` 型から `Integer` 型に変換する場合、`CType` 関数では小数点以下を銀行型丸め（近い方の整数に、半整数 .5 は偶数になるように丸める）によって取り除くが、C# では切り捨てられる。

## [データ型変換関数](https://docs.microsoft.com/ja-jp/dotnet/visual-basic/language-reference/functions/type-conversion-functions)（CStr, CInt, ...）

- `CInt(obj)` と `CType(obj , Integer)` は等価（ほかの型についても同様）。
- `CInt` の小数→整数は銀行型丸め。

# ヘルパークラス／メソッドによる変換

## Parse, ParseExact, TryParse, TryParseExact メソッド

- 文字列から変換。
- `DateTime` 型の場合、`ParseExact` で書式を明示指定して変換することができる。
- `Nothing` を渡すと `ArgumentNullException` が発生する。

## ToString メソッド

- 文字列化。
- `Enum` の `ToString()` はメンバ名になる。
- `Decimal` の `ToString` で末尾のゼロを取り除く（`1.0` → `"1"`）には、書式 `"G29"` を指定する。（.NET 1.1 以上の場合）

## [Convert クラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.convert)
- 基本データ型を別の基本データ型に変換する。
- 内部的に `Parse` メソッドを呼んでいる。
- 小数から整数への変換は銀行型丸め。

## [TypeConverter クラス](https://docs.microsoft.com/ja-jp/dotnet/api/system.componentmodel.typeconverter)

- コントロールのデザインプロパティの文字列解析などに使用されている。
- `TypeDescriptor.GetConverter(Type)` メソッドで、実行時に型（`Type` オブジェクト）を指定して `TypeConverter` オブジェクトを取得することができる。
- 文字列からの変換に `ConvertFromString(String)` メソッドが用意されているが、既定で用意されたコンバーターの許容範囲は狭い。桁区切りカンマ、通貨記号、全角は数値の構成要素として解釈されず、小数から整数へのキャストもできない。

## [Microsoft.VisualBasic.Conversion.Int メソッド](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.visualbasic.conversion.int)

- 負の値で切り捨てにならない。負でも切り捨てるには `Fix` 関数を使う。

## [Microsoft.VisualBasic.Conversion.Val メソッド](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.visualbasic.conversion.val)

- VB6時代から想定外の挙動で何かと問題を引き起こしてきた。`Val("1,234")` の結果は `Double` の `1` である。
- 手軽で許容範囲が広い反面、意図しない変換結果になることがあるので、使用には注意が必要。

# 暗黙的な変換

## 暗黙の数値変換（.NET 共通）

- 暗黙的に変換可能な型の対応が「<a href="https://docs.microsoft.com/ja-jp/dotnet/standard/base-types/conversion-tables" target="_blank" class="default">.NET の型変換の表 | Microsoft Docs</a>」に拡大変換／縮小変換と分けて記載されている。
- 拡大変換の多くは情報を失うことなく実行できるが、浮動小数点数値型への変換では精度が損なわれるケースもある。
- 縮小変換では情報が失われる可能性がある。変換元の値が変換先の型の `MaxValue` と `MinValue` の範囲外にある場合は `OverflowException` が発生する。
- `Integer` のリテラル、定数値が変換先の型の範囲内にある場合、暗黙的に変換できる。範囲外の場合はコンパイルエラーとなる。

## Option Strict Off

- Off だと意図しない型変換が暗黙的に行われ、バグの温床になる。On にすることが推奨されている。
- 既定は Off なので、プロジェクトを作成したら [コンパイル オプション] 設定で On にするのを忘れないようにする。
    - たまに忘れて後で気づく。VB6からの移行を考慮してのことなのかもしれないが、そろそろプロジェクトの既定は On に変えてほしい。