VB.NETでもCType演算子をオーバーロードして値を変換したい
はじめに
これまた特に需要は無かったんですが、意味なくクラスを別のクラスへ変換したかったんでCType
について調べてみました。
演算子と関数
なんか調べてみると自分でオーバーロードするCType
演算子と変換を実行するCType
関数の二種類があるみたいですね。
Operator Statement | Microsoft Docs
CType 関数 (Visual Basic) | Microsoft Docs
自分で変換を定義するときは演算子オーバーロードとしてCType
を実装し、明示的に呼び出すときはCType
関数を用いて変換を指示すると自分で定義したCType
演算子があればそれが呼ばれるし、それ以外にもプリミティブ型であればいい感じにコンパイラが等価なMSILとしてインラインに展開するみたいな感じっぽい?
VBのことだしやっぱりCType("1.0", Double)
とかやるとみんな大好きCompilerServices
がしゃしゃり出てくるのでそう単純じゃないっぽいんですけどね。えぇ。
演算子の実装の方はと言いますと、ずいぶん前にOption Strict
特集で取り上げて変換には拡大変換と縮小変換の二種類があるってやりました。*1
CType
の方も同じように演算子を定義するときにWidening
とNarrowing
というキーワードを使用してその変換が拡大変換か縮小変換かコンパイラに教えてやらなくてはなりませぬ。
拡大変換の方 Widening (Visual Basic) | Microsoft Docs
縮小変換の方 Narrowing (Visual Basic) | Microsoft Docs
拡大変換は常に成功しなくちゃならないとか縮小変換は失敗する可能性があるとかその辺のお話しはOption Strict
特集でどうぞ。
ちなみに特定の値へ変換する愉快な関数たちについての説明も見つけたので合わせてどうぞ。
データ型変換関数 (Visual Basic) | Microsoft Docs
サンプルコード
とりあえずいろいろ試してみるためのサンプルがこちら
Module Module1 Sub Main() ' プロジェクトの設定で Option Strict On にしてます Conversion() Console.WriteLine() Casting() ' 継承関係にないものはいずれもムリ 'Dim e As HogeE = New HogeE(1) 'Dim f As HogeF = e 'Dim f As HogeF = DirectCast(e, HogeE) 'Dim f As HogeF = CType(e, HogeE) Console.ReadLine() End Sub Public Sub Conversion() ' 拡大変換なので特に文句は言われない ' ちなみに値の変換は変換を行った時点でCType演算子によって ' 相手の型のインスタンスが生成されるので参照が指し示す ' インスタンスは別々のもの ' 明示的にしろ暗黙的にしろ CType が呼ばれる Dim a1 As HogeA = New HogeA(Integer.MaxValue) Dim b1 As HogeB = a1 a1.Display() b1.Display() Console.WriteLine() ' 縮小変換としてCType演算子をオーバーロードしたので ' 暗黙の型変換はコンパイラによって禁止される ' ので、明示的に変換をしてやる必要がある Dim b2 As HogeB = New HogeB(Long.MaxValue) b2.Display() Try Dim a2 As HogeA = CType(b2, HogeA) ' 例外が発生した時点で制御は例外ハンドラのCatchのほうへ移っちゃう ' ので↓は実行されません a2.Display() Catch ex As OverflowException Console.WriteLine("まぁ、結局オーバーフローしちゃうんですけどね") End Try ' 余談ですが、Tryまで入力してVisual Studioが挿入する例外ハンドラの ' スニペットが ' > Catch ex As Exception ' なのはちょっと問題だと思う ' 例外についてよくわかってないんだろうな~この人って人が書いたコードで ' 例外ハンドラがすべからく Exception をキャッチしているのを見たときに ' そうおもいましたまる ' 継承関係がないのでこっちはムリぽ ' Dim b3 As HogeB = DirectCast(a1, HogeB) End Sub Public Sub Casting() ' 参照を変換している方 ' スーパークラスの参照からサブクラスへの参照への変換は ' 明示的に行わなくてはならない ' ちなみに参照をいじってるだけなので変数に格納されている参照が ' 指し示すインスタンスは4つとも同一 Dim c As HogeC = New HogeD(1) Dim dCType As HogeC = CType(c, HogeD) Dim dDirectCast As HogeC = DirectCast(c, HogeD) Dim dTryCast As HogeC = TryCast(c, HogeD) c.Display() dCType.Display() dDirectCast.Display() dTryCast.Display() End Sub End Module ' 値として変換を定義 Class HogeA Private value As Integer Public Sub New(value As Integer) Me.value = value End Sub Public Sub Display() Console.WriteLine(value) End Sub Public Shared Widening Operator CType(source As HogeA) As HogeB ' 拡大変換であり変換時にエラーが生じることはないので Widening で ' CType演算子をオーバーロード ' Widening で宣言するとコンパイラは Option Strict が On でも ' 暗黙的に変換することを許可する Return New HogeB(source.value) End Operator End Class Class HogeB Private value As Long Public Sub New(value As Long) Me.value = value End Sub Public Sub Display() Console.WriteLine(value) End Sub Public Shared Narrowing Operator CType(source As HogeB) As HogeA ' ここで System.OverflowException の可能性があるので ' 演算子は Narrowing で宣言 ' Narrowingの場合は変換時にエラーの発生する可能性があるというこ ' となので Option Strict が On の場合はコンパイラは暗黙的に変換 ' を禁止する Dim v = CType(source.value, Integer) Return New HogeA(v) End Operator End Class ' 継承関係にあるので参照の変換が可能 Class HogeC Protected value As Integer Public Sub New(value As Integer) Me.value = value End Sub Public Overridable Sub Display() Console.WriteLine("HogeC:{0}", value) End Sub End Class Class HogeD Inherits HogeC Public Sub New(value As Integer) MyBase.New(value) End Sub Public Overrides Sub Display() Console.WriteLine("HogeD:{0}", value) End Sub End Class ' 定義は同じだけど継承関係も値として変換するための ' 演算子の定義もしてない全く関係のないクラス Class HogeE Private value As Integer Public Sub New(value As Integer) Me.value = value End Sub End Class Class HogeF Private value As Integer Public Sub New(value As Integer) Me.value = value End Sub End Class ' 継承もするし変換も定義する ' 継承関係にあるクラスへの CType 演算子は許されないんDA☆ 'Class HogeG ' Private value As Integer ' Public Sub New(value As Integer) ' Me.value = value ' End Sub ' Public Shared Widening Operator CType(g As HogeG) As HogeH ' Return New HogeH(g.value) ' End Operator 'End Class 'Class HogeH ' Inherits HogeG ' Private value As Integer ' Public Sub New(value As Integer) ' MyBase.New(value) ' End Sub ' Public Shared Widening Operator CType(h As HogeH) As HogeG ' Return New HogeG(h.value) ' End Operator 'End Class
要点だけまとめてみると
CType
関数を呼び出すとDirectCast
演算子を呼び出すと参照の型の変換のみとなる(変換に失敗した場合は例外をスローする)TryCast
演算子は参照の型の変換を試み、失敗した場合はNothing
を返す- 継承関係にあるクラスへの
CType
演算子の定義は許されない
って感じみたいです。
おまけでDirectCast
とTryCast
の説明もどうそ。
DirectCast 演算子 (Visual Basic) | Microsoft Docs
TryCast 演算子 (Visual Basic) | Microsoft Docs
まとめ
用語の定義や使い方が色々とふわふわした感じで非常に気持ち悪いですが、一応独自クラスから独自クラスへの値としての変換を実装できました。 ただ、多分実用で実装することは滅多にないと思います。リフレクションの方が多用しそうですね。
ちなみにちなみにこれは勝手な想像ですがDirectCast
やTryCast
は等価なMSILに置き換えられるだけなので演算子、CType
はコンパイラが内部的に適切なメソッド呼び出しやMSILに置き換えるため関数ってことなんですかね。
勝手な想像ですよ。根拠はどこにも無いですし。
おわり
*1:ページの最後にリンクを張っておきました。