F# 可区分联合的拆箱值

Vag*_*lov 5 reflection f# discriminated-union

我的 F# 代码的某些函数接收装箱为对象的值,即使输入了基础值也是如此。如果该值是可区分联合,则无法将其拆箱回 F# 类型。这是一个简单的例子:

type Result<'TOk,'TError> = 
| Ok of 'TOk 
| Error of 'TError

type ResultA = Result<string, int>

let a = Ok "A"
let o = box a

match o with
| :? ResultA -> printfn "match ResultA"
// | :? ResultA.Ok -> printfn "match" // doesn't compile
| _ when o.GetType().DeclaringType = typedefof<ResultA> -> printfn "match via reflection"
| _ -> printfn "no match"
Run Code Online (Sandbox Code Playgroud)

此示例的输出是“通过反射匹配”,ResultA 永远不会匹配,因为装箱值属于不同的 CLR 类型 - Result.Ok。由于 F# 可区分联合案例表示为其自己的类型,因此装箱值与 ResultA 类型不匹配。此外,不可能将其与 ResultA.OK 匹配,因为在 F# 代码中它不是合法类型。唯一的选择似乎是使用反射手动实例化值,这是低效且愚蠢的,因为该值已经实例化,它就在这里,只是一旦装箱就无法在 F# 代码中访问它。

我是否忽略了什么?是否有更直接的方法来拆箱 F# 可区分联合值?

Fyo*_*kin 4

你只是匹配了不同的类型。您的变量a不是类型,而是泛型类型,装箱时会被强制转换为泛型类型ResultAResult<string, 'a>Result<string, obj>

要么显式地使变量具有正确的类型:

let a : ResultA = Ok "A"
Run Code Online (Sandbox Code Playgroud)

或者匹配正确的类型:

match o with
| :? Result<string, obj> -> printfn "match ResultA"
Run Code Online (Sandbox Code Playgroud)

两种选择都有效。


关于您的假设的注释

ResultA 永远不会匹配,因为装箱值属于不同的 CLR 类型 - Result.Ok

原因不是这个。is类型匹配的工作方式与 C# 中的/运算符相同as- 即它匹配子类型以及确切类型。DU 成员被编译为 DU 类型本身的子类型。这就是 F# 如何让 .NET 将不同的情况作为一种类型来处理。

关于运行时类型的一般注意事项
不需要在运行时处理类型。如果可能的话,尽量避免它。经验法则应该是,如果您发现自己在运行时处理类型,则您可能建模了错误的东西。

如果您不确切知道一切是如何运作的,则尤其如此。