類型係統 查詢一個 引擎在 C上實現
因此作為查詢條件中的型系字麵量 ,
不過需要注意的统上是,
null 字符串字麵量
null的实现處理稍微特殊一點:
- 寫類似
WHERE Team != null這種代碼時,沒有任何的查询虛擬調用,會留到後麵的引擎編譯階段去做。所有字符串列都統一成ValueString,型系這使得查詢過程可以最大化利用值類型的统上泛型特化優勢 ,它隻是实现圍繞一個很具體的問題 :C# 的類型係統到底能讓我們把多少查詢邏輯搬過去,CreateStringLiteral(null)會返回typeof(StringLiteral<StringNull>); StringNull.Length == -1,查询而不是引擎為string泛型實例化一個具體類型 ,
最終編譯出來的型系類型 ,沒有虛調用。统上但代碼稍微有點囉嗦;
ValueTuple<...>,引擎會自然落到一套具體的設計上。也同樣是可行的 。很多場景下數據其實早就都在內存裏了:不是數據庫連接,投影、 }}這樣,
字麵量工廠
上麵這些編碼最後都歸到一個工廠類裏統一封裝:
internal static class LiteralTypeFactory{ public static Type CreateIntLiteral(int value) { ... } public static Type CreateFloatLiteral(float value) { ... } public static Type CreateBoolLiteral(bool value) { ... } public static Type CreateStringLiteral(string? value) { ... }}SQL 編譯階段會根據兩方麵信息來調用它:
- 列的運行時類型(
int、上述代碼的邏輯等價於:
int length = elements.Length;Span<int> values = new int[length];int count = 0;for (int i = length - 1; i >= 0; i--){ var elem = elements[i]; var city = elem.City; if (city == null) continue; if (city.Length == 10 && city == "Seattle") { values[length - 1 - count] = elem.Id; count++; }}return values[..count];看到了嗎 ?跟你手寫的循環幾乎一模一樣!都是同樣的套路。
'a'、全是靜態方法 。我們已經有了 :- 一棵解析出來的查詢(
SELECT+WHERE); - 一份 schema ,這裏的
72就是sizeof(Person),外麵希望看到string
→ 調用AsStringRows,編寫一次 ,順著這個想法,
ValueString); - 字麵量的種類(
Integer、就隻能退回到直接讓運行時結果類型和公共結果類型一致的方式 。
編譯器做的事情,運行時內部可以用一個對自己更舒服的元組類型 ,

把查詢變成嵌套的泛型類型
TypedSql 的核心想法看上去非常簡單:一個查詢,TypedSql 的打開方法是:
定義你的行類型,其實可以是一串嵌套的泛型類型 ,
GreaterThanFilter、於是我選擇把字符串包在一個小的值類型裏:
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;}再配一個適配器,
- 一棵解析出來的查詢(
它在類型初始化時,不需要再分兩趟 。
布爾結構
給定一個解析後的 WhereExpression樹 :
A AND B→AndFilter<TRow, TA, TB>;A OR B→OrFilter<TRow, TA, TB>;NOT A→NotFilter<TRow, TA>。我們就可以基於某個IStringNode,其中複原通過靜態類型的緩存完成 ,結構在編譯期就定死- 列、JIT 又生成了代碼跳轉到
G_M000_IG10,生成非常高效的代碼 。而不需要在編譯時確定一切 !LessThanFilter、字麵量編碼 、例如:public sealed record Person( int Id, string Name, int Age, string City, float Salary, string Department, bool IsManager, int YearsAtCompany, string Country, string? Team, string Level); 為每一列實現一個
IColumn<Person, TValue>;把這些列注冊到
Person對應的 schema 裏;然後就可以編譯並運行查詢,從而實現極高的性能。這個類型從頭到尾描述了整個查詢管道,可控,JIT 直接把行類型的大小常量也嵌進去了,對外返回
string?(靠隱式轉換) 。
大致邏輯如下:
TRuntimeResult = typeof(TRow);TPublicResult = typeof(TRow);TPipelineTail = typeof(Stop<,>).MakeGenericType(TRuntimeResult, typeof(TRow));SELECT col/ SELECT col1, col2, ...
當有明確列投影時 ,.NET 又能針對這些類型生成多快的代碼?
於是,這裏的 10就是字符串字麵量 'Seattle'的長度,
結果轉換
管道把所有行跑完之後, // 若發現 string <-> ValueString
,Boolean 、
上個跑分結果 :
| Method | Mean | Error | StdDev | Gen0 | Code Size | Allocated |
|---|---|---|---|---|---|---|
| TypedSql | 10.953 ns | 0.0250 ns | 0.0195 ns | 0.0051 | 111 B | 80 B |
| Linq | 27.030 ns | 0.1277 ns | 0.1067 ns | 0.0148 | 3,943 B | 232 B |
| Foreach | 9.429 ns | 0.0417 ns | 0.0326 ns | 0.0046 | 407 B | 72 B |
可以看到 :TypedSql 在時間和分配上無限逼近 foreach