string へのキャストと ToString の動作は異なる

いつまでも危ない(?)PDFを晒しておくわけには行かないのでw、小ネタでブログを進めます。

時々(特にVBプログラマの方?)では、文字列へのキャストを ToString で行っている箇所を見掛けるのですが、これは危ういです、という話ですね。

例えば、データベースから読み込みをした時に VB だと、こういう風に書いているのです。

Dim da as new SqlDataAdapter("SELECT * ...", cn )
Dim dt as new DataTable
da.Fill(dt)
' 文字列を取り出す
dim name as String = ""
if dt.Rows.Count > 0 then
  name = dt.Rows(0)("name").ToString()
end if

こんな風に、ToString メソッドを使って object 型を文字列に変換しています。
ただし、本来はここはキャストを使うべきです。後述しますが、キャストと ToString メソッドの【意味】が異なるので、必ずしも同じ動作をするとは限らないのためです。

dim name as string = CType( dt.Rows(0)("name"), String )

C# の場合は、

string name = (string)dt.Rows[0]["name"];

# 詳細に言えば、dynamic cast を使うんでしょうが、ここでは普通のキャストで。

さて、ToString でも用途は足りるので、これでも良いような気がしますが、何故キャストを使わないといけないかというと、以下の理由があります。

dim name As String = obj.ToString()

とした時に、name には必ず期待する【文字列】が入るかというと、実は異なるのです。これは、ListBox を扱うと分かるのですが、ToString メソッドはオーバーライド可能なので、単純なキャストとは異なる値を入れることができるのです。

動作が分かるように極端な例を示すと、

Public Class AClass
    Public Shadows Function ToString() As String
        Return "ToString AClass"
    End Function

    Public Shared Narrowing Operator CType(ByVal b As AClass) As String
        Return "Cast AClass"
    End Operator
End Class

ToString をオーバーライドしたものと、キャストを再定義したものを用意します。
すると次のコードでは実行結果が違ってきます。

        Dim a As New AClass
        ' ToString の場合
        Debug.Print(a.ToString())
        ' Cast の場合
        Debug.Print(CType(a, String))

▼実行結果

ToString AClass
Cast AClass

こんな風に、ToString が定義されている時は、思ったとおりには動かないのです。まぁ、こういう時は、String 型へのキャスト自体も危ういところなのですが、ひとまず ToString とキャストは違う動作をする、ってことを覚えてコーディングして欲しいなぁ、と。

余談を言えば、キャストの場合は string 型にキャストできない場合は例外を発生させるけど、ToString 型は例外にはならない(多分ならない)ですよね。このあたり、意図して ToString メソッド(ToIntegerメソッドとかと同列の意味で)を使う分には ok ってことなのです。

カテゴリー: 開発 パーマリンク