我正在处理从二进制文件读取的一些数据,并且可以是几种原始类型之一,例如int16,int32,单,双等。
例如,说我有3个文件:
每个文件的标头都有用于数据类型的代码,例如,文件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()
查看一种称为动态实例化的东西-这是一个实例,人们可能会使用它吗?
答案 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
,传入一个对象并取回一个对象。