匹配列表类型的 FSharp 模式

FSharp pattern that matches List type

我想匹配类型为"obj"的对象obj1,根据其实际类型匹配。 问题是列表类型的类型检查模式(下例中的第二个)与 F# 列表不匹配。

let obj1 = [1;2;3] :> obj
match obj1 with
    | :? System.Array as a -> printfn "it's an array: %A" a
    | :? List<_> as l -> printfn "It's a list: %A" l
    | _ -> printfn "other type"

输出 "other type",而我希望它是 "It's a list: [1;2;3]"

如何正确检查列表类型?

我不会将 List<_> 称为 F# 列表 - 它是 .NET 框架类型。您可以将它与 (int list) 类型匹配——它是 F# list:

match obj1 with | :? (int list) as l -> 1 | _ -> 0
obj : [any type]

您需要做的是区分您的 obj 是否为通用类型。通过 .GetType() 获取它的类型,在那里你会找到合适的 属性.

obj : [some type of the form T<'T1, 'T2, ..., 'Tn> for concrete types T1, T2, ..., Tn]

如果是,那么您可以通过对通过 .GetType()

获得的类型使用适当的方法来获得泛型类型定义
obj : [some type of the form T<'T1, 'T2, ..., 'Tn> with T1, T2, ..., Tn not bound yet]

并且您现在可以将此类型与 typedefof<_ list> 进行比较(_ list 被推断为 obj list,但是 typedefoftypeof 不同已经为您获取了通用类型定义)。

最终代码看起来像这样(伪代码)

let ty = obj.GetType()
match obj with
| unknownType when not ty.IsGenericType -> "some non-generic type, probably a concrete collection"
| list when ty.GetGenericTypeDefinition() = typedefof<_ list> -> "list"
| array when ty.GetGenericTypeDefinition() = typedefof<_ array> -> "array"
| _ -> "something else, i.e. a generic type that we don't know"

Daniel Fabian 已经在他的回答中解释了这个问题。

实现他的解决方案的一种方法是使用 Active Pattern:

let (|IsList|_|) (candidate : obj) =
    let t = candidate.GetType()
    if t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<list<_>>
    then Some (candidate :?> System.Collections.IEnumerable)
    else None

您现在可以更改匹配以使用此活动模式:

let obj1 = [1;2;3] :> obj
match obj1 with
    | :? System.Array as a -> printfn "it's an array: %A" a
    | IsList l -> printfn "It's a list: %A" l
    | _ -> printfn "other type"

这会打印:

> It's a list: [1; 2; 3]