VB のたまご

作成日: 2015/12/03, 更新日: 2015/12/03


InvalidCastException について

概要

  •  InvalidCastException は、型変換の処理ができない場合に発生する例外エラーです。 変換元の型にセットされた値が、変換先の型に合っていない値の場合です。 プリミティブ型( Date、Integer、String 等の基本的なデータ型のこと)の型変換は、原因が分かりやすいので良いのですが、 継承元・継承先の型変換は、原因がよく分からない場合が多いのではないでしょうか。このことを知ることが、解決の糸口につながります。


スポンサーリンク


事例と対処方法

  •  以下のサンプルは、意図的に例外エラーを発生させるコードです。 実際の業務プログラムでは、このような簡単なコードではないと思いますが、例外発生させている、直接的な原因となる部分として参考になるはずです。 また、本例外エラーが発生してしまう原因となった、根本的な原因となる別の例外エラーが、さらに奥深くにいる場合があります。 (本例外エラーは間接的に影響を受けて発生してしまったもので、本当の原因となる例外エラーを食い止めることで、本例外エラーも直る場合があります。)

  •  このような組み合わせのパターンは、本例外エラーに限らず、他の例外エラー全般にも言えることですが、そんなに頻繁に出くわすものではないと思いますので、 そういう場合もあるということだけ、覚えておいていただけると解決しやすくなるのではと思います。

  •  また、記載の対処方法は、あくまでも一例です。 他の対処方法の方が、その業務プログラムにとってベストプラクティスとなる場合もありますので、検討材料の1つという程度に確認していただければと思います。

  • スポンサーリンク



  • ArrayList
  • Dim lst As New ArrayList(New Integer() {1, 2, 3})
    For Each oneData In lst
        Dim d As Date = CType(oneData, Date)
    Next
    
    ' 例外エラー
    System.InvalidCastException: 型 'Integer' から型 'Date' への変換は無効です。
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToDate(Object Value)
    
    ' 対処方法
    変換先の型を、変換元の値をセットできる適切な型に、変更する
    
  •  変換元の型が Object 型の場合は要注意です。何の型のデータがセットされているのか分かりません。 Object 型は何でもセットできる便利なものですが、その反面、値の取得時は、同じくらい不便な型でもあります。 ArrayList の使用は控えて、ジェネリックリストを使うべき。と言われる理由の1つです。 型チェック( If TypeOf oneData Is Integer Then )することでも例外エラーを回避することはできますが、 根本的な原因としては、やはり受け取り先の型の再考ではないかと考えます。


  • Date
  • Dim o1 As Object = #12/31/2015#
    Dim i1 As Integer = CType(o1, Integer)
    
    ' 例外エラー
    System.InvalidCastException: 型 'Date' から型 'Integer' への変換は無効です。
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(Object Value)
    
    ' 対処方法
    変換先の型を、変換元の値をセットできる適切な型に、変更する
    
  •  これは、前の事例と同様です。


  • String、その1
  • Dim o1 As Object = "aaa"
    Dim i1 As Integer = CType(o1, Integer)
    
    ' 例外エラー
    System.InvalidCastException: String "aaa" から型 'Integer' への変換は無効です。 ---> System.FormatException: 入力文字列の形式が正しくありません。
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ParseDouble(String Value, NumberFormatInfo NumberFormat)
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)
       --- 内部例外スタック トレースの終わり ---
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(Object Value)
    
    ' 対処方法
    変換先の型を、変換元の値をセットできる適切な型に、変更する
    
  •  原因は前の事例と同様ですが、String 型の場合はエラー内容が違っています。 例えば、文字列で「12」の場合は例外エラーは発生しません。 つまり、「String 型の値」までは正しかったのですが、文字列のフォーマットが数字以外だったので、フォーマットに関する例外エラーが発生することになりました。 よって、これは、FormatException の例外エラーを受けての、InvalidCastException の例外エラーとなります。 型変換する前に実施される、フォーマット確認の事前チェック時に引っかかった事例になります。


  • String、その2
  • Dim i As Integer = 0
    Dim s As String = String.Empty
    
    s = "1" ' OK
    s = "一" ' NG
    s = "壱" ' NG
    i = CInt(s)
    
    ' 例外エラー
    System.InvalidCastException: String "壱" から型 'Integer' への変換は無効です。 ---> System.FormatException: 入力文字列の形式が正しくありません。
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ParseDouble(String Value, NumberFormatInfo NumberFormat)
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)
       --- 内部例外スタック トレースの終わり ---
       場所 Microsoft.VisualBasic.CompilerServices.Conversions.ToInteger(String Value)
    
    ' 対処方法
    変換先の型を、変換元の値をセットできる適切な型に、変更する
    
  •  何となくできそうな気もする、と思ってしまいますが、前の事例と同様です。


  • 継承元、継承先
  • ' 継承元・親クラス
    Public Class BaseData
        Public Property Name As String = ""
    End Class
    
    ' 継承先・子クラス1
    Public Class AppData
        Inherits BaseData
        Public Property Age As Integer = 0
    End Class
    
    ' 継承先・子クラス2
    Public Class SysData
        Inherits BaseData
        Public Property Number As Integer = 0
    End Class
    
    Dim base As New BaseData
    Dim app As New AppData
    
    ' OK アップキャスト(継承先 → 継承元)
    ' AppData 型は BaseData 型の機能を含んでいるため、型変換が可能。
    Dim a1 As BaseData = CType(app, BaseData)
    
    ' OK ダウンキャスト(継承元 → 継承先)
    ' a1 のインスタンスは、元をたどると AppData 型のインスタンス。同じ型同士一致するので、型変換が可能。
    Dim a2 As AppData = CType(a1, AppData)
    
    ' NG ダウンキャスト(継承元 → 継承先)
    ' なんとなくできそうな感じがするが、
    ' base のインスタンスは、元をたどると BaseData 型のインスタンス。BaseData 型は AppData 型の機能を含んでいないため、型変換は不可能。
    Dim a3 As AppData = CType(base, AppData)
    
    ' これは、こちらの説明の方が分かりやすいかも。
    ' base のインスタンスは、AppData 型とは違う継承先の SysData 型のインスタンスが入っている。
    ' 違う型同士では型変換できない。
    base = New SysData
    Dim a4 As AppData = CType(base, AppData) ' 同じ親クラスからの派生でも、派生先が違えば別物扱い。
    
    ' 例外エラー
    System.InvalidCastException: 型 'WindowsApplication319.BaseData' のオブジェクトを型 'WindowsApplication319.AppData' にキャストできません。
    
    ' 対処方法
    Dim a3 As AppData = TryCast(base, AppData)
    If a3 IsNot Nothing Then
        Console.WriteLine("型変換できた。")
    End If
    ' a4 も同様。
    
  •  型変換のことをキャストと言います。また、継承先から継承元にキャストすることを、アップキャストと言い、 継承元から継承先にキャストすることを、ダウンキャストと言います。 よく、複数の継承先クラスをまとめて処理したい場合に、継承元クラスやインターフェースを使って、共通の処理を行いますが、 このような場合に多く発生します。

  •  コード中にコメントで説明していますが、継承元、継承先は親子関係にあたり、それぞれの型をインスタンス生成してセットすることができます。
  • Dim base As BaseData = New AppData
    Dim app As AppData = CType(New BaseData, AppData)
    
  •  ただし、継承元から継承先へのインスタンス生成時は、暗黙的にはセットできず、明示的にキャスト経由でセットしないとコンパイルエラーとなります。 また、この場合も例外エラー発生してしまいますが理由は同様です。

  •  最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク