F#中如何通过参数统计discriminated union列表的对应标签元素数量
解决方案
问题原因
F#的可区分联合(DU)的所有标签实例,调用GetType()返回的都是外层DU类型(带载荷的标签虽然会生成嵌套子类,但直接用运行时类型匹配并不稳定,也不是官方推荐的DU标签区分方式),所以你原来的方案无法正确区分不同标签。
核心思路
使用F#官方提供的DU专属反射API来获取实例对应的标签信息,不需要硬编码匹配分支,新增DU分支后无需修改统计代码。
前置依赖
需要引入F#反射命名空间:
open Microsoft.FSharp.Reflection
实现通用统计方法
1. 基础工具函数
首先实现获取任意DU实例对应标签信息的通用函数:
let getUnionCase (duInstance: 'T) = FSharpValue.GetUnionFields(duInstance, typeof<'T>) |> fst
该函数返回UnionCaseInfo类型,是F#中用来唯一标识DU标签的标准类型,不受载荷影响,无载荷和带载荷的DU都可以正常使用。
2. 单标签统计函数
你需要的按指定标签统计数量的函数实现如下:
let countOfCase (targetTag: UnionCaseInfo) (duList: 'T list) = duList |> List.filter (fun item -> getUnionCase item = targetTag) |> List.length
3. 按标签名统计(更易用的封装)
如果不想手动处理UnionCaseInfo,可以封装为按标签名称字符串统计的版本:
let countOfCaseByName (tagName: string) (duList: 'T list) = let allTags = FSharpType.GetUnionCases(typeof<'T>) match allTags |> Array.tryFind (fun t -> t.Name = tagName) with | Some tag -> countOfCase tag duList | None -> 0
使用时直接传入标签名称即可,比如统计A标签的数量:
let aCount = countOfCaseByName "A" yourTList
4. 一次性统计所有标签数量
针对你需要统计所有标签数量的场景,可以直接用List.countBy实现全量统计,完全不需要硬编码任何分支:
let countAllTags<'T> (duList: 'T list) = duList |> List.countBy getUnionCase |> Map.ofList
返回结果是Map<UnionCaseInfo, int>类型,你可以通过标签名或者UnionCaseInfo直接读取对应数量,新增DU分支后不需要修改任何代码即可自动统计新标签。
性能优化
如果你的场景更新频率高,担心反射开销,可以提前缓存对应DU类型的所有标签信息,避免重复反射:
// 仅初始化时执行一次反射,缓存T类型的所有标签 let tTagCache = FSharpType.GetUnionCases(typeof<T>) |> Array.map (fun tag -> tag.Name, tag) |> Map.ofArray // 优化后的按名统计函数,无额外反射开销 let countOfCaseByNameFast tagName duList = match tTagCache.TryFind tagName with | Some tag -> countOfCase tag duList | None -> 0
带载荷DU兼容性
以上所有实现对带载荷的DU完全兼容,无论载荷是值类型、引用类型还是自定义类型,都可以正确识别标签,不需要做任何修改。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

