我以'a option when 'a :> IBaseType
的形式得到了一些计算结果。有一个类型的树派生自IBaseType
,我真的不知道这个特定类型的选项,但重要的是它是特定派生的选项,而不是基本类型。所以我想将它转发到IBaseType option
以进一步处理它。由于选项是泛型类型,因此不可能直接进行强制转换(在F#中),而且我必须在Option.map中进行强制转换。没有什么复杂的,类型推断按预期工作......
中间铸造选项也按预期解决了......
直到功能完成。此时由于某种原因,类型推断决定原始选项必须已经是IBaseType option
类型:
中级类型早先已经解决,为什么决定重新分配op
的感染类型?当然这会导致运行时异常。看起来像编译器错误,但主要规则是编译器中没有错误。
所以最后这听起来真的很愚蠢:我没有想法如何简单地改造简单的选项。只是为了让图片更清晰:processResult
以IBaseType option
为参数。这是麻烦功能的来源:
(fun (x: obj) ->
let op = x :?> _ option
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
upcastOp |> processResult)
有任何想法如何解决这个问题?
答案 0 :(得分:3)
你是如何制作盒装物品的?最简单的解决方案是打开IBaseType option
而不是装箱#IBaseType option
来开始。如果由于某种原因这是不可行的,那么你可能需要使用反射。问题是在这段代码中:
let op = x :?> _ option
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
编译器知道op
对于某些'a option when 'a :> IBaseType
是'a
但是没有什么能让编译器弄清'a
实际上是什么,因为这种类型不是反映在函数的最终输出中 - 编译器需要提交'a
的特定类型,并且它可以做出的最佳猜测只是基类型IBaseType
。你需要做这样的事情:
type ResultProcessor =
static member ProcessResult<'a when 'a :> IBaseType> (op:'a option) =
let upcastOp = op |> Option.map (fun y -> y :> IBaseType)
upcastOp |> processResult
fun (x:obj) ->
let ty = x.GetType() // must be option<something>
let [| tyArg |] = ty.GetGenericArguments()
typeof<ResultProcessor>.GetMethod("ProcessResult").MakeGenericMethod(tyArg).Invoke(null, [|x|])
答案 1 :(得分:1)
我不知道如何推断op
的类型。
但我很确定如果你不能按照kvb的建议改变x
到IBaseType option
的类型,你真的必须使用反射。
另一种基于反射的解决方案:
let f (x:obj) =
match x with
| null -> None // x is None
| _ -> match x.GetType().GetProperty("Value") with
| null -> None // x is not an option
| prop ->
let v = prop.GetValue( x, null )
Some (v :?> IBaseType)
答案 2 :(得分:1)
我支持kvb的解决方案。现在,在我为类似代码做的一些基准测试中,为了绝对性能,我发现避免动态(未知)方法调用是一个优势。以某种方式制作泛型类型的新实例更快。例如:
[<AbstractClass>]
type BaseResultProcessor() =
abstract member ProcessResult : obj -> option<IBaseType>
[<Sealed>]
type ResultProcessor<'T when 'T :> IBaseType>() =
inherit BaseResultProcessor()
override this.ProcessResult(x: obj) =
match x :?> option<'T> with
| Some x -> Some (x :> IBaseType)
| None -> None
module Example =
let run (x: obj) =
let ty = x.GetType()
let tyArg = ty.GetGenericArguments().[0]
let p =
typedefof<ResultProcessor<_>>.MakeGenericType(tyArg)
|> Activator.CreateInstance :?> BaseResultProcessor
p.ProcessResult(x)
至于问题是什么,以下“直观”推理在.NET中无效:
'T1 :> 'T2
--------------------------
option<'T1> :> option<'T2>
我想说这是典型的类型系统 - 一旦你考虑到它们与整个类型系统的交互,那些看似简单或直观的东西很难或不可能正确实现。