Object型を内部処理形式にキャスト への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 mayopee  (社会人) 投稿日時 2019/3/22 19:08:54
ありがとうございます。教えてもらった以下の方法で希望通りの結果が得られました。

>あるいは型指定した DataTable ではなく、素の DataTable を用いていて、
>型が動的に決まるので自動判定したいという話なら、
>   Dim t As Type = arr2D(1, 1).GetType()
>などとして Type を得られますので、それを Columns.Add に渡して定義するという手も。

他の指摘事項についても、すべて了解しました。
どうもありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/22 16:04:53
> これを型指定されたDataTableに格納したいのです。

こういうことでしょうか。

'あらかじめ、As Object(,) から 行単位に 1 次元化しておく 
Dim Items As Object() = {1, True"A"}

Dim row = dt.New表名Row()

'型指定な DataTable のために、個別に型を明示する方法もありますが 
'row.列名1 = CInt(Items(0)) 
'row.列名2 = CBool(Items(1)) 
'row.列名3 = CStr(Items(2)) 

'下記の手抜き実装でも十分です。(型チェックは DataTable 側に任せることになります) 
row.ItemArray = Items

dt.Add表名Row(row)



あるいは型指定した DataTable ではなく、素の DataTable を用いていて、
型が動的に決まるので自動判定したいという話なら、
  Dim t As Type = arr2D(1, 1).GetType()
などとして Type を得られますので、それを Columns.Add に渡して定義するという手も。


> Dim arr2D = TryCast(xlRange.Value, Object(,)) '1オリジンになることに注意 
> Dim rowCount = arr2D.GetLength(0) '行数 

これだと、NullReferenceException の可能性がありますね。
(xlRange が単一セルの場合など)

「Object(,) に変換できない場合」を想定していないのであれば、
TryCast を使う必要は無く、直接 DirectCast すれば十分かと思います。
型が違っていれば InvalidCastException になるので、むしろ分かりやすいのではないでしょうか。
Dim arr2D = DirectCast(xlRange.Value, Object(,))
Dim rowCount = arr2D.GetLength(0)



もしも TryCast するのであれば、変換できなかった場合に Nothing が返されることを
考慮したコードになっているべきですので、TryCast に続く行では、
 If arr2D IsNot Nothing Then
で判定するようにするとか、あるいは、
 'Dim rowCount = If(arr2D, New Object(-1, -1) {}).GetLength(0)
 Dim rowCount = If(arr2D?.GetLength(0), -1) 
のように、Nothing への対処を盛り込んだ方が良いでしょう。



> '1次元配列に変換 
> Dim arr1D = arr2D.Cast(Of Object)

1 次元配列への変換なら、
 Dim arr1D = arr2D.Cast(Of Object)().ToArray()
ですね。

元のコードだと、Dim arr1D As Object() にはなりません。
As IEnumerable(Of Object) として扱うことはできますが。


> Dim heder = Enumerable.Range(1, columnCount).Select(Function(x) $"列{x.ToString}").ToArray

$"列{x.ToString}" だと冗長なので、
$"列{x}" または $"列{x:D}" をお奨めしておきます。


> dt.Columns.Add(heder(i).ToString, GetType(String))

これも heder(i).ToString ではなく、header(i) だけで良いかと。
変数 header は「As String()」な型なので、ToString メソッドは冗長です。

String 値をわざわざ ToString() する必要性は無いはず…。
dt.Columns.Add(heder(i).ToString().ToString().ToString().ToString().ToString(), GetType(String))
投稿者 (削除されました)  () 投稿日時 2019/3/22 15:45:49
(削除されました)
投稿者 mayopee  (社会人) 投稿日時 2019/3/22 12:44:24
解り難くて、申し訳ありません。
今やっているのは、「EXCELの矩形範囲のデータをDataTableに格納する」という事です。
これがExcel95形式の読み取りパスワード付きのファイルなのでOLEDBやClosedXML、NPOI
といったライブラリを使う事も出来ずCOM経由でデーターを取得しています。
COMから返却されるデータは問答無用で1オリジンのObject型2次元配列になります。
これを型指定されたDataTableに格納したいのです。

長くなりますが、一部省略したコードを載せます。環境はVB2017です。
以下のようなイメージで処理しています。(COMの解放は省いています)
以下ではフィールドの型が文字列としてなら取得できています。
  
 
    Public Function ReadExcellCOM(FilePath As String, SheetName As String, Address As String, Password As StringAs DataTable
        Dim xlApp As Microsoft.Office.Interop.Excel.Application = Nothing
        Dim xlBooks As Microsoft.Office.Interop.Excel.Workbooks = Nothing
        Dim xlBook As Microsoft.Office.Interop.Excel.Workbook = Nothing
        Dim xlSheets As Microsoft.Office.Interop.Excel.Sheets = Nothing
        Dim xlSheet As Microsoft.Office.Interop.Excel.Worksheet = Nothing
        Dim xlRange As Microsoft.Office.Interop.Excel.Range = Nothing
        Dim dt As New DataTable
        xlApp = New Microsoft.Office.Interop.Excel.Application
        'ウィンドウの位置を(-500, -500)に、サイズを100x100に変更する 
        MoveWindow(New IntPtr(xlApp.Hwnd), -500, -500, 100, 100, 0)
        xlApp.Visible = True
        xlApp.DisplayAlerts = False
        xlBooks = xlApp.Workbooks
        '既存のファイルを開く 
        xlBook = xlBooks.Open(FilePath, Password:=Password)
        xlSheets = xlBook.Worksheets
        xlSheet = CType(xlSheets(SheetName), Microsoft.Office.Interop.Excel.Worksheet)
        xlRange = xlSheet.Range(Address)  'データの入力セル範囲 
        Dim arr2D = TryCast(xlRange.Value, Object(,)) '1オリジンになることに注意 
        Dim rowCount = arr2D.GetLength(0) '行数 
        Dim columnCount = arr2D.GetLength(1) '列数 
        '1次元配列に変換 
        Dim arr1D = arr2D.Cast(Of Object)
        '列作成 
        Dim heder = Enumerable.Range(1, columnCount).Select(Function(x) $"列{x.ToString}").ToArray
        For i = 0 To heder.Length - 1
            dt.Columns.Add(heder(i).ToString, GetType(String))
        Next
        '1オリジンを0オリジンに変換 
        Dim arr2D0 = arr1D.ToArray.masaTo2DArray(rowCount, columnCount)
        ' DataRowの作成 
        For r = 0 To rowCount - 1
            Dim row As DataRow = dt.NewRow
            For c = 0 To columnCount - 1
                row(heder(c).ToString) = arr2D0(r, c)?.ToString
            Next
            dt.Rows.Add(row)
        Next
        dt.AcceptChanges()
        Return dt
    End Function
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2019/3/22 11:45:47
質問の意味と意図が分かりませんでした。

例えば
 Dim a As Integer
 Dim b As Boolean
 Dim c As String
のような変数があって、Dim o As Object の中身に応じて、
どの変数にセットするかを切り分けたいという事でしょうか?


変数のデータ型はコンパイル時に確定されているものであって、
実行時に動的に変えられるものではありませんよね。
 Dim x As 継承元の型 = 派生クラスのインスタンス
のようなことならできますが…。(Object 型はすべての型の基底クラス)


どういう状況で、何のためにその処理が必要なのかを
明確にしてもらえると、より具体的な回答が出来るかもしれません。
たとえばジェネリックで対応するとか、TypeOf あるいは GetType 等で切り分けるとか。
投稿者 mayopee  (社会人) 投稿日時 2019/3/22 11:01:25
Object型を内部処理形式にキャストしたいです。
例えば、【Object {Integer}型】を【Integer型】にキャストしたいです。
以下のようなコードで実験してみましたが、(1)のようにあらかじめ型が解っている場合はこれで
キャストできましたが、実行時にしか型が判明しないような場合はどうすれば良いでしょうか?
リフレクションを用いて処理できないでしょうか?

     'Object {Integer}型の変数を用意 
        Dim o As Object = CObj(1I)
        'Integerにキャスト 
        Dim v1 = CType(o, Integer'(1)△ あらかじめ型が解っている場合はこれでOK 
        Dim v2 = Convert.ChangeType(o, o.GetType'(2)× Object {Integer}のまま