
.NET 里写查询的时候很多场景下数据其实早就都在内存里了不是数据库连接也不是某个远程服务的结果而就是一个数组或者ListT。我只是想过滤一下、投影一下。这时候通常有几种选择写一个foreach循环 —— 性能好、可控但代码稍微有点啰嗦用 LINQ —— 写起来舒服看起来也优雅就是有迭代器、委托带来的那点开销要么干脆极端一点把数据塞进数据库再写真正的 SQL这听起来就有点反直觉……但是我想尝试一条完全不同的思路如果我们把 C# 的类型系统本身当成查询计划会怎样也就是说不是像平时那样在运行时构建一棵表达式树再拿着这棵树去解释执行整个查询而是写一段 SQL 风格的字符串把它编译成一个类型这个类型从头到尾描述了整个查询管道然后所有实际运行时的逻辑都走静态方法。这个想法最终促成了 TypedSql —— 一个用 C# 类型系统实现的内存内 SQL 查询引擎。把查询变成嵌套的泛型类型#TypedSql 的核心想法看上去非常简单一个查询其实可以是一串嵌套的泛型类型比如WhereSelectTRow, …, Stop...这样。顺着这个想法再往下推几步会自然落到一套具体的设计上。把执行计划塞进类型系统#在 TypedSql 里每一个编译好的查询最终都会变成一个封闭的泛型管道类型。这个管道是由一些基础节点拼出来的比如WhereTRow, TPredicate, TNext, TResult, TRootSelectTRow, TProjection, TNext, TMiddle, TResult, TRootWhereSelectTRow, TPredicate, TProjection, TNext, TMiddle, TResult, TRootStopTResult, TRoot每个节点都实现了同一个接口Copyinternal interface IQueryNodeTRow, TResult, TRoot { static abstract void Run(ReadOnlySpanTRow rows, scoped ref QueryRuntimeTResult runtime); static abstract void Process(in TRow row, scoped ref QueryRuntimeTResult runtime); }这里可以简单理解成Run是外面那一圈大循环整体遍历Process是对单行执行的逻辑。比如Where节点大概长这样Copyinternal readonly struct WhereTRow, TPredicate, TNext, TResult, TRoot : IQueryNodeTRow, TResult, TRoot where TPredicate : IFilterTRow where TNext : IQueryNodeTRow, TResult, TRoot { public static void Run(ReadOnlySpanTRow rows, scoped ref QueryRuntimeTResult runtime) { for (var i 0; i rows.Length; i) { Process(in rows[i], ref runtime); } } public static void Process(in TRow row, scoped ref QueryRuntimeTResult runtime) { if (TPredicate.Evaluate(in row)) { TNext.Process(in row, ref runtime); } } }关键点在于管道的形状完全藏在这些类型参数里面每个节点是一个只有静态方法的struct—— 不需要创建实例没有虚调用。对 JIT 来说一旦这些泛型类型参数都被代入这就是一张普通的静态调用图而已。列和投影#查询总得运行在某种行类型TRow上这通常是你自己定义的一个 record/class/struct。每一列会实现这样一个接口Copyinternal interface IColumnTRow, TValue { static abstract string Identifier { get; } static abstract TValue Get(in TRow row); }举个简单的例子Copyinternal readonly struct PersonNameColumn : IColumnPerson, string { public static string Identifier Name; public static string Get(in Person row) row.Name; }而投影SELECT后面那部分则实现Copyinternal interface IProjectionTRow, TResult { static abstract TResult Project(in TRow row); }将选出某一列本身做成一个投影可以这么写Copyinternal readonly struct ColumnProjectionTColumn, TRow, TValue : IProjectionTRow, TValue where TColumn : IColumnTRow, TValue { public static TValue Project(in TRow row) TColumn.Get(row); }多列选择时TypedSql 会构造专门的投影把结果拼成ValueTupleCopyinternal readonly struct ValueTupleProjectionTRow, TColumn1, TValue1 : IProjectionTRow, ValueTupleTValue1 where TColumn1 : IColumnTRow, TValue1 { public static ValueTupleTValue1 Project(in TRow row) new(TColumn1.Get(row)); } // … 一直到 7 列然后通过一个“Rest”再递归挂一个 IProjection还是同样的模式全是struct全是静态方法。过滤器#过滤器的接口长这样Copyinternal interface IFilterTRow { static abstract bool Evaluate(in TRow row); }一个最常用的比较过滤器形式是列 字面量Copyinternal readonly struct EqualsFilterTRow, TColumn, TLiteral, TValue : IFilterTRow where TColumn : IColumnTRow, TValue where TLiteral : ILiteralTValue where TValue : IEquatableTValue, IComparableTValue { [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool Evaluate(in TRow row) { if (typeof(TValue).IsValueType) { return TColumn.Get(row).Equals(TLiteral.Value); } else { var left TColumn.Get(row); var right TLiteral.Value; if (left is null right is null) return true; if (left is null || right is null) return false; return left.Equals(right); } } }这里我们通过判断TValue是值类型还是引用类型来分别处理null的情况。.NET 的 JIT 能够识别这种模式并且为值类型和引用类型分别特化并生成不同的代码路径从而实际上并不存在任何的分支开销。GreaterThanFilter、LessThanFilter、GreaterOrEqualFilter、LessOrEqualFilter、NotEqualFilter等等都是同样的套路。逻辑运算也是在类型层面组合的Copyinternal readonly struct AndFilterTRow, TLeft, TRight : IFilterTRow where TLeft : IFilterTRow where TRight : IFilterTRow { public static bool Evaluate(in TRow row) TLeft.Evaluate(in row) TRight.Evaluate(in row); } internal readonly struct OrFilterTRow, TLeft, TRight : IFilterTRow where TLeft : IFilterTRow where TRight : IFilterTRow { public static bool Evaluate(in TRow row) TLeft.Evaluate(in row) || TRight.Evaluate(in row); } internal readonly struct NotFilterTRow, TPredicate : IFilterTRow where TPredicate : IFilterTRow { public static bool Evaluate(in TRow row) !TPredicate.Evaluate(in row);