【问题标题】:Downcasting "obj" to underlying array type w/o type annotation将“obj”向下转换为没有类型注释的底层数组类型
【发布时间】:2019-03-12 04:28:30
【问题描述】:

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

例如,假设我有 3 个文件:

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

每个文件的标题都有一个数据类型的代码,例如,文件 A 会有一个标题字段 dtype: 0,文件 B 会有一个标题字段 dtype: 1,文件 C 会有一个标题字段 @987654323 @。

这些文件是专有的图像格式 - 想象一个位图,但位图的主体可能都是 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&lt;obj&gt;(例如 int[][]

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

我需要将这个obj 恢复为数组类型。问题是,由于我正在读取多种可能类型的文件,因此无法将我的代码概括为适用于所有不同的文件。如果我知道该文件将包含所有整数,我知道我可以做到container.[0] :?&gt; int[][],但我在设计时不知道。

我知道我不能将数组的类型存储为来自 GetType() 的 let 绑定,这进一步混淆了我应该如何处理这个问题(例如,container.[0] :?&gt; 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

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

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

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

编辑 3:我一直在使用 Activator.CreateInstance() 研究一种称为动态实例化的东西 - 这是一个可以使用它的实例吗?

【问题讨论】:

  • 目前还不清楚您真正想要做什么。您是否在阅读元素都是不同类型的,但又想以某种方式使它们成为一种类型?还是每个给定文件都只包含一种类型的元素?
  • @FyodorSoikin 每个给定文件只包含一种类型的元素。我会尝试在问题正文中清除它。
  • 你希望对沮丧的结果做什么?
  • @kvb 由于文件中的数据代表 3D 或 4D 数组(文件格式是专有的医学成像格式,如果有帮助的话),我希望能够在数组。将数组作为 obj 不会让我这样做,并且代码无法知道数据是 int16、int32、single 等,直到解析文件头(幸运的是,我没有问题)。我担心来自 MATLAB/Mathematica 让我对一些更惯用的方法视而不见,但它让我无法理解。

标签: .net types casting f#


【解决方案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[][] -&gt; 't

【讨论】:

  • 我对使用 DU 的前景感到非常兴奋,但我对 DU 类型的问题是它们不可枚举,所以我不能使用 Item 或 GetSlice 方法,我不'认为DUs不能扩展。反射是我最初关于解决对象问题的想法,但在 500mb 阵列上进行反射似乎是一个可能的性能瓶颈,此时我只想使用反射来一次向下投射整个阵列。如果有办法在我的 DU 案例中使用 IEnumerable,那么我认为这绝对是最好的方法。
  • @normalchemist - 你不能直接使用这些方法,但你可以编写一个'a[][] -&gt; something 类型的通用函数,然后将该函数与你所拥有的任何联合案例一起使用(例如我在哪里已经在容器上设置了for 循环,你会遇到像| I16 arr -&gt; myFunction arr 这样的情况。
  • 一个微妙之处在于,如果您的函数具有像 'a[][] -&gt; 'a[] 这样的类型,那么您的三个输出将具有不同的类型(int[]int16[]single[]),因此您将需要将它们装箱或为一维数组创建新的联合类型。但是,如果函数的类型为 'a[][] -&gt; float 或类似的类型,则不需要这样做,因为您会在每个分支中获得一个浮点数。
  • 所以如果我使用DU解决方案,我真的别无选择,只能围绕类型跳舞?似乎如果我使用 DU 解决方案,我将失去使用数组方法处理数组的任何机会,但如果我使用反射方法,那么我将进行大量装箱和拆箱?对不起,如果这是一个乏味的问题 - 这对我来说仍然是一个非常新的、非典型的方法。
  • @normalchemist - 我不这么看。同样,最终扩展您实际尝试对数组执行的操作可能对您有所帮助。最容易做的事情(如果可能的话)是使您的数组处理完全通用(也就是说,能够对 any 类型 't't[][] 进行操作)。然后,当您遍历各个数组时,您可以执行一次强制转换,然后在结果上调用通用函数,而无需跟踪中间异构列表,这是导致您出现问题的原因。
【解决方案2】:

当您无法在编译时确定类型时,请执行运行时类型测试,可能结合as 模式绑定到变量。由于我们获取了类型信息,再扔掉会很浪费,所以我们把它存储在某种数据结构中。

对于 sum 类型的编码,这里特别是 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|]|]

【讨论】:

  • 我会尝试进一步编辑我的问题。我想你可能想告诉我我认为我需要的东西,但我是强类型语言的新手,所以我会稍微梳理一下。
  • 这不是我想要做的——这段代码似乎只有在将某个数字类型的数组转换为字节,然后再转换回数字数组类型时才有效。如果你给它一个简单的字节数组,这个代码将不起作用。 :(
  • 您的编辑无疑帮助我指明了正确的方向。现在唯一的障碍是如何从每个 DU 案例中获取元素。不幸的是,我没有在 ArrayTypes 上实现 IEnumerable...
猜你喜欢
  • 2021-02-02
  • 2019-01-07
  • 2014-02-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多