はじめに
これまた特に需要は無かったんですが、意味なくクラスを別のクラスへ変換したかったんで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:ページの最後にリンクを張っておきました。