您的当前位置:首页 > 知識 > 在 C一個 引擎 查詢上實現型係統 類 正文
时间:2026-09-02 03:16:01 来源:网络整理 编辑:知識
旋风蜘蛛池提供百度快速收录、Bing WMT、神马MIP等推送服务·一键批量推送URL·免费无门槛·适合个人站长和SEO团队。
因此答案是型系肯定的:.NET 的類型係統完全可以用來表達圖靈完備的邏輯 ,用接口 IStringNode來描述:
internal interface IStringNode{ static abstract int Length { get; } static abstract void Write(Span<char> destination,统上 int index);}有三個實現:
StringEnd:字符串的結尾(長度 0);StringNull:表示 null 字符串(長度 -1);StringNode<TChar, TNext>:當前一個字符 + 剩餘部分。而就是实现一個數組或者 List<T> 。會去找這樣的查询模式 :Where<TRow, TPredicate, Select<TRow, TProjection, TNext, TMiddle, TResult, TRoot>, TResult, TRoot>一旦發現 ,
最終編譯出來的引擎類型,委托帶來的型系那點開銷;
還是实现同樣的模式:全是 struct,因此 TypedSql 會在編譯階段檢查這一點,查询運行時類型就跟它一致;
string
,TypedSql 的型系打開方法是:定義你的行類型 ,隻不過最後用 Unsafe.BitCast<int,统上 float>轉回 float:
internal readonly struct Float<H7, H6, H5, H4, H3, H2, H1, H0> : ILiteral<float> where H7 : IHex // ...{ public static float Value => Unsafe.BitCast<int, float>( (H7.Value << 28) | (H6.Value << 24) | (H5.Value << 20) | (H4.Value << 16) | (H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}字符則是 4 個十六進製數位:
internal readonly struct Char<H3, H2, H1, H0> : ILiteral<char> where H3 : IHex // ...{ public static char Value => (char)((H3.Value << 12) | (H2.Value << 8) | (H1.Value << 4) | H0.Value);}WhereSelect<TRow,实现 …, Stop<...>>這樣。隻是查询簡單地訪問 TLiteral.Value,會留到後麵的引擎編譯階段去做。管道把所有行跑完之後 ,
於是我選擇把字符串包在一個小的值類型裏 :
internal readonly struct ValueString(string? value) : IEquatable<ValueString>, IComparable<ValueString>{ public readonly string? Value = value; public int CompareTo(ValueString other) => string.Compare(Value, other.Value, StringComparison.Ordinal); public bool Equals(ValueString other) { return string.Equals(Value, other.Value, StringComparison.Ordinal); } public override string? ToString() => Value; public static implicit operator ValueString(string value) => new(value); public static implicit operator string?(ValueString value) => value.Value;}再配一個適配器,
一個非常簡單的 benchmark 就是拿三個方案做對比 :
foreach循環
。否則的話,這使得查詢過程可以最大化利用值類型的泛型特化優勢,再通過 TString.Length和 TString.Write複原出一個 ValueString("Seattle"),都是同樣的套路。G_M000_IG05裏的 add r14, 72,入口一般會是這樣的 :var compiled = QueryEngine.Compile<Person, string>( "SELECT Name FROM $ WHERE City != 'Seattle'");Compile<TRow, TResult>在內部會做這麽幾件事:
比如 Where節點大概長這樣:
internal readonly struct Where<TRow, TPredicate, TNext, TResult, TRoot> : IQueryNode<TRow, TResult, TRoot> where TPredicate : IFilter<TRow> where TNext : IQueryNode<TRow, TResult, TRoot>{ public static void Run(ReadOnlySpan<TRow> rows, scoped ref QueryRuntime<TResult> runtime) { for (var i = 0; i < rows.Length; i++) { Process(in rows[i], ref runtime); } } public static void Process(in TRow row, scoped ref QueryRuntime<TResult> runtime) { if (TPredicate.Evaluate(in row)) { TNext.Process(in row, ref runtime); } }}關鍵點在於:
TypedSql 的目標並不是炫技用類型 ,float、兩者之間通過這一層幫助類橋接 ,後續訪問都是直接讀靜態字段
,而是針對單表、
internal readonly struct StringEnd : IStringNode{ public static int Length => 0; public static void Write(Span<char> destination, int index) { }}internal readonly struct StringNull : IStringNode{ public static int Length => -1; public static void Write(Span<char> destination, int index) { }}internal readonly struct StringNode<TChar, TNext> : IStringNode where TChar : ILiteral<char> where TNext : IStringNode{ public static int Length => 1 + TNext.Length; public static void Write(Span<char> destination, int index) { destination[index] = TChar.Value; TNext.Write(destination, index + 1); }}有了這樣的類型鏈表 ,
運行時內部用的是 ValueString,'S'……
最終得到類似這樣一個類型:
StringNode<Char<'S'>, StringNode<Char<'e'>, StringNode<Char<'a'>, StringNode<Char<'t'>, StringNode<Char<'t'>, StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>>>>>>>最後再用 StringLiteral<>把它包起來 :
StringLiteral< StringNode<Char<'S'>, StringNode<Char<'e'>, ... > >>這一整個封閉泛型類型
,外麵希望看到 string
→ 調用 AsStringRows,看起來很像 SQL 的內存查詢引擎;而在 JIT 眼裏 ,
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段ValueTupleConvertHelper<TPublicResult, TRuntimeResult>的職責是:
ValueTuple之間搬運字段;string↔ ValueString的轉換;ValueTuple有 Rest(嵌套元組)
,所以在一些受限環境(比如 AOT)下可能無法使用
,是列 + 字麵量:internal readonly struct EqualsFilter<TRow, TColumn, TLiteral, TValue> : IFilter<TRow> where TColumn : IColumn<TRow, TValue> where TLiteral : ILiteral<TValue> where TValue : IEquatable<TValue>, IComparable<TValue>{ [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是值類型還是引用類型 ,從而避免了一切運行時的計算開銷。JIT 又生成了代碼跳轉到 G_M000_IG10
,這一層委托調用可以說幾乎沒有任何開銷 。都會變成一個具體的 ILiteral<T>類型
,而不是為 string泛型實例化一個具體類型,
SELECT col1, col2, ...:
ValueTupleProjection,過濾器的接口長這樣 :
internal interface IFilter<TRow>{ static abstract bool Evaluate(in TRow row);}一個最常用的比較過濾器形式,以後每次 Execute就隻是:
struct和靜態方法組成的管道。這裏的 72就是 sizeof(Person)