将“ obj”向下转换为不带类型注释的基础数组类型

时间:2018-10-07 07:29:30

标签: .net types casting f#

我正在处理从二进制文件读取的一些数据,并且可以是几种原始类型之一,例如int16,int32,单,双等。

例如,说我有3个文件:

  • 文件A:包含所有int16
  • 文件B:包含所有int32
  • 文件C:包含所有单个

每个文件的标头都有用于数据类型的代码,例如,文件A将具有标头字段dtype: 0,文件B将具有标头字段dtype: 1,文件C将具有标头字段标头字段dtype: 2

文件是专有的图像格式-想象一下位图,但是位图的主体可以全部是int16,int32或单个。

鉴于我已经从二进制文件中读取了数据,所以我使用模式匹配将二进制数据转换为文件头指定的类型。

说我有一个包含从文件流读取的n个字节的数组/缓冲区:

let buffer: byte[] = … <-- read bytes into here
let container = new ResizeArray<obj>() //maintain same type in pattern match

let matchDatatype (dtype: int) = // Let's read File B
    match dtype with
    | 0 ->             
        let typeBuffer: int16[] = numBytesInFile/2 |> Array.zeroCreate
        while stream.Position < stream.Length do
            stream.Read(buffer, 0, numBytesInFile) |> ignore
            Buffer.BlockCopy(buffer, 0, typeBuffer, 0, numBytesInFile)
            typeBuffer |> Array.chunkBySize 8 |> container.Add
    | 1 -> // Reading int32 from File B
        let typeBuffer: int32[] = numBytesInFile/4 |> Array.zeroCreate
        while stream.Position < stream.Length do
            stream.Read(buffer, 0, numBytesInFile) |> ignore
            Buffer.BlockCopy(buffer, 0, typeBuffer, 0, numBytesInFile)
            typeBuffer |> Array.chunkBySize 8 |> container.Add
    | 2 -> ...
    ….

因此,如果我从文件B读取,则标题数据代码会说“将这些字节读取为int32”,还会发生其他事情,并且我有一个ResizeArray<obj>,其中包含数组数组(例如int[][]

要获取我需要的数组,只需将ResizeArray(container.[0])切片,然后得到一个obj

我需要将此obj恢复为数组类型。问题在于,由于我正在读取多种可能的类型的文件,因此我难以将我的代码推广到所有不同文件中。我知道如果文件包含所有整数,就可以container.[0] :?> int[][],但是在设计时我不知道。

我知道我无法将数组的类型存储为GetType()的let绑定,这进一步混淆了我应该如何使用它(例如container.[0] :?> container.[0].GetType()不起作用)。

使用fsi,下面是一个示例,说明我从文件B(int)中读取的内容:

> let someArray = [|[|0;1;2|];|[3;4;5|]|];; <-- say I read this from File B
  val it : int [] [] = [|[|0; 1; 2|];[|3; 4; 5|]|]

> container.Add(someArray)
  val it: unit = ()

> let arrObj = container.[0]
  val it : obj = [|[|0; 1; 2|];[|3; 4; 5|]|]

> arrObj.GetType().FullName;;
  val it : string = "System.Int32[][]"

> arrObj :?> int[][] <-- I can't know this at design time
  val it : int [] [] = [|[|0; 1; 2|];[|3; 4; 5|]|]

最后一步是发生问题的地方。返回的类型对象清楚地表明它知道该数组不是对象-实际上它是int[][]。如何以编程/动态方式执行此向下转换,而无需明确地说出“向下转换为int[][]”?我也需要使用它来处理single [] []和int16 [] []情况。

或者,在拥有可以灵活读取不同类型数据的代码方面,我的整个方法是否有缺陷?我唯一的其他想法是做一些尝试捕获的怪物,但是我觉得那不是很习惯。

我以前的工作都是在MATLAB中完成的,所以对我来说这是一个新问题,因为我可以评估字符串并生成所需的代码。

编辑:使用Buffer.BlockCopy代替BitConverter

编辑2 :我看到F#可以使用type定义类型别名,其中

[accessibility-modifier] type-abbreviation = type-name

但是,这不能让我做类似type ArrType = arrObj.GetType()的事情。我能想到的最接近我需要的是类似C的typedef。

编辑3 :我一直在使用Activator.CreateInstance()查看一种称为动态实例化的东西-这是一个实例,人们可能会使用它吗?

2 个答案:

答案 0 :(得分:3)

当无法在编译时确定类型时,请执行运行时类型测试,并可能与绑定到变量的as模式结合使用。由于我们获取类型信息,因此再次丢弃它会很浪费,因此我们将其存储在某些数据结构中。

对于求和类型(尤其是int16[][]int32[][]float32[][]中的任何一种的编码),F#提供了discriminated union

type ArrayTypes =
| I16 of int16[][]
| I32 of int32[][]
| F32 of float32[][]

let arrayTypes : obj -> _ = function
| :? (int16[][]) as i16 -> I16 i16
| :? (int32[][]) as i32 -> I32 i32
| :? (float32[][]) as f32 -> F32 f32
| _ -> invalidOp "Unknown Array Type"

arrayTypes <| box[|[|0s;1s;2s|];[|3s;4s;5s|]|]
// val it : ArrayTypes = I16 [|[|0s; 1s; 2s|]; [|3s; 4s; 5s|]|]
arrayTypes <| box[|[|0;1;2|];[|3;4;5|]|]
// val it : ArrayTypes = I32 [|[|0; 1; 2|]; [|3; 4; 5|]|]
arrayTypes <| box[|[|0.f;1.f;2.f|];[|3.f;4.f;5.f|]|]
// val it : ArrayTypes = F32 [|[|0.0f; 1.0f; 2.0f|]; [|3.0f; 4.0f; 5.0f|]|]

答案 1 :(得分:1)

我认为kaefer所暗示的观点是最惯用的。将container声明为

let container = new ResizeArray<ArrayTypes>()

,现在您可以以强类型正确包含每个数组。您的matchDataType方法也可以重构为更类似的

let add sz c = 
    let typeBuffer = numBytesInFile/sz |> Array.zeroCreate
    while stream.Position < stream.Length do
        stream.Read(buffer, 0, numBytesInFile) |> ignore
        Buffer.BlockCopy(buffer, 0, typeBuffer, 0, numBytesInFile)
        typeBuffer |> Array.chunkBySize 8 |> c |>  container.Add

let matchDatatype (dtype: int) = // Let's read File B
    match dtype with
    | 0 ->             
       add 2 I16
    | 1 -> // Reading int32 from File B
       add 4 I32 

然后,当您处理列表时,您可以执行以下操作:

for t in container do
    match t with
    | I16 arr -> // do something with arr: int16[][]
    | I32 arr -> // do something with arr:   int[][]
    | F32 arr -> // do something with arr:single[][]

考虑到您描述问题的方式,我认为这可能是最干净的方法。

但是,可以使用称为“反射”的技术动态地做事:

type T() = 
    static member DoSomethingWithAnArray<'t>(arr:'t[][]) = arr.[0].[0]

let doSomethingWithAnArray (arr:obj) = 
    let meth = typeof<T>.GetMethod("DoSomethingWithAnArray")
    // for simplicity, I'm not actually checking that arr is an array of arrays
    // but you could use IsArray and GetArrayRank twice to be sure
    let elementType = arr.GetType().GetElementType().GetElementType()
    meth.MakeGenericMethod(elementType).Invoke(null, [|arr|])

doSomethingWithAnArray(box [|[|1.0|]|])
|> printfn "%A"

在这里,即使此方法具有签名T.DoSomethingWithAnArray,我们也会动态调用't[][] -> 't,传入一个对象并取回一个对象。