F#泛型实现TSqlFragmentVisitor报FS3213错误的优雅方案
F# 封装 TSqlFragmentVisitor 简化访问器编写方案
报错原因
TSqlFragmentVisitor类的Visit不是参数为基类TSqlFragment的单虚方法,而是为每一种语法节点类型单独定义的重载方法。普通泛型版本即使加了基类约束,编译器也无法在泛型定义阶段确定要重写哪一个重载,因此会报匹配多个方法的错误。
实现方式
给泛型函数加inline关键字即可解决问题:内联函数会在每个调用点根据指定的具体节点类型生成专属实现,此时编译器可以明确匹配到对应类型的Visit重载完成重写,没有额外反射开销,和手写代码的性能完全一致。
open Microsoft.SqlServer.TransactSql.ScriptDom open System.Collections.Generic /// 生成指定TSql节点类型的访问器,返回收集结果的列表和访问器实例 let inline createVisitor<'T when 'T :> TSqlFragment> () = let collected = ResizeArray<'T>() let visitor = { new TSqlFragmentVisitor() with member _.Visit(node: 'T) = collected.Add(node) } collected, visitor
使用时只需要指定要收集的节点类型即可,不需要重复写模板代码:
// 收集CREATE ASSEMBLY语句 let assemblyList, assemblyVisitor = createVisitor<CreateAssemblyStatement>() // 收集EXECUTE语句 let executeList, executeVisitor = createVisitor<ExecuteStatement>() // 收集SELECT语句 let selectList, selectVisitor = createVisitor<SelectStatement>()
如果要进一步简化解析流程,可以再封装一层直接提取节点的工具函数:
let inline parseAllNodes<'T when 'T :> TSqlFragment> (sqlText: string, sqlVersion: int = 160) = let parser = match sqlVersion with | 160 -> TSql160Parser(false) :> TSqlParser | 150 -> TSql150Parser(false) :> TSqlParser | 140 -> TSql140Parser(false) :> TSqlParser | _ -> failwith "不支持的SQL Server版本" use reader = new System.IO.StringReader(sqlText) let mutable parseErrors = Unchecked.defaultof<_> let script = parser.Parse(reader, &parseErrors) if parseErrors.Count > 0 then failwithf "SQL解析失败,错误数:%d" parseErrors.Count let nodes, visitor = createVisitor<'T>() script.Accept(visitor) nodes.ToArray()
调用示例:
let allCreateAssemblyStmts = parseAllNodes<CreateAssemblyStatement> "你的SQL脚本内容"
内容的提问来源于stack exchange,提问作者user19344869
相关产品推荐
相关产品推荐

