いつまでも危ない(?)PDFを晒しておくわけには行かないのでw、小ネタでブログを進めます。
時々(特にVBプログラマの方?)では、文字列へのキャストを ToString で行っている箇所を見掛けるのですが、これは危ういです、という話ですね。
例えば、データベースから読み込みをした時に VB だと、こういう風に書いているのです。
1 2 3 4 5 6 7 8 | 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 メソッドの【意味】が異なるので、必ずしも同じ動作をするとは限らないのためです。
1 | dim name as string = CType ( dt.Rows(0)( "name" ), String ) |
C# の場合は、
1 | string name = ( string )dt.Rows[0][ "name" ]; |
# 詳細に言えば、dynamic cast を使うんでしょうが、ここでは普通のキャストで。
さて、ToString でも用途は足りるので、これでも良いような気がしますが、何故キャストを使わないといけないかというと、以下の理由があります。
1 | dim name As String = obj.ToString() |
とした時に、name には必ず期待する【文字列】が入るかというと、実は異なるのです。これは、ListBox を扱うと分かるのですが、ToString メソッドはオーバーライド可能なので、単純なキャストとは異なる値を入れることができるのです。
動作が分かるように極端な例を示すと、
1 2 3 4 5 6 7 8 9 | 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 をオーバーライドしたものと、キャストを再定義したものを用意します。
すると次のコードでは実行結果が違ってきます。
1 2 3 4 5 | Dim a As New AClass ' ToString の場合 Debug.Print(a.ToString()) ' Cast の場合 Debug.Print( CType (a, String )) |
▼実行結果
1 2 | ToString AClass Cast AClass |
こんな風に、ToString が定義されている時は、思ったとおりには動かないのです。まぁ、こういう時は、String 型へのキャスト自体も危ういところなのですが、ひとまず ToString とキャストは違う動作をする、ってことを覚えてコーディングして欲しいなぁ、と。
余談を言えば、キャストの場合は string 型にキャストできない場合は例外を発生させるけど、ToString 型は例外にはならない(多分ならない)ですよね。このあたり、意図して ToString メソッド(ToIntegerメソッドとかと同列の意味で)を使う分には ok ってことなのです。