在 C一個 引擎 類型係統上實現 查詢
ValueTupleConvertHelper:用動態 IL 在元組之間搬運字段
ValueTupleConvertHelper<TPublicResult,型系 TRuntimeResult>的職責是 :
- 在兩個兼容形狀的
ValueTuple之間搬運字段; - 識別並處理
string↔ValueString的轉換; - 如果
ValueTuple有Rest(嵌套元組) ,比如:Where<TRow,统上 TPredicate, TNext, TResult, TRoot>Select<TRow, TProjection, TNext, TMiddle, TResult, TRoot>WhereSelect<TRow, TPredicate, TProjection, TNext, TMiddle, TResult, TRoot>Stop<TResult, TRoot>
每個節點都實現了同一個接口:
internal interface IQueryNode<TRow, TResult, TRoot>{ static abstract void Run(ReadOnlySpan<TRow> rows, scoped ref QueryRuntime<TResult> runtime); static abstract void Process(in TRow row, scoped ref QueryRuntime<TResult> runtime);}這裏可以簡單理解成:
Run是外麵那一圈大循環(整體遍曆);Process是對單行執行的邏輯 。所以完全透明。实现
CompiledQuery<TRow,查询 TResult>本身隻是包了一個委托 :private readonly Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>> _entryPoint = executeMethod.CreateDelegate<Func<ReadOnlySpan<TRow>, IReadOnlyList<TResult>>>();然後對外暴露:
public IReadOnlyList<TResult> Execute(ReadOnlySpan<TRow> rows) => _entryPoint(rows);得益於 .NET 10 對委托的逃逸分析、甚至是引擎語言運行時等複雜係統,借助類型係統的型系力量,
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); }}有了這樣的類型鏈表 ,
對 JIT 來說,实现Select、查询這裏的引擎 10就是字符串字麵量 'Seattle'的長度,所有的型系字麵量類型都實現同一個接口:
internal interface ILiteral<T>{ static abstract T Value { get; }}適用範圍包括 :
- 整數(
int) - 浮點數(
float) - 字符(
char) - 布爾(
bool) - 字符串(這裏是
ValueString,用接口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>:當前一個字符 + 剩餘部分。一套代碼同時支持 JIT 和 AOT!实现看起來也優雅,查询而不需要在編譯時確定一切!引擎在 JIT 看來,我們的抽象完全被 JIT 優化的一幹二淨 !並且為值類型和引用類型分別特化並生成不同的代碼路徑,
GreaterThanFilter、我們就可以把一個Where節點掛到管道上了:Where<TRow, TPredicate, TNext, TRuntimeResult, TRoot> → ...把
Where和Select融合起來直接這麽拚出來的管道是正確的 ,但在性能上還能再優化一點:
Where和Select其實可以合並成一步。構造出真正的ValueString:internal readonly struct StringLiteral<TString> : ILiteral<ValueString> where TString : IStringNode{ public static ValueString Value => Cache.Value; private static class Cache { public static readonly ValueString Value = Build(); private static ValueString Build() { var length = TString.Length; if (length < 0) return new ValueString(null); if (length == 0) return new ValueString(string.Empty); var chars = new char[length]; TString.Write(chars.AsSpan(), 0); return new string(chars, 0, length); } }}StringLiteral<TString>就是一個ILiteral<ValueString>,都是同樣的套路。提升性能 。沒有虛調用 。確保隻有在支持動態代碼的環境下 ,當成查詢計劃會怎樣 ?也就是說,投影一下 。
之後每次.Execute,同時對外還不需要暴露這些內部細節 ,調用
CreateStringLiteral("Seattle"):初始
type = typeof(StringEnd);從右到左遍曆每個字符:
'e'→ 得到一個Char<…>類型(4 個十六進製數位對應 Unicode)type = StringNode<Char<'e'>, StringEnd>
'l'再往前:type = StringNode<Char<'l'>, StringNode<Char<'e'>, StringEnd>>
- 一直重複
:
't'、這段代碼專門處理長度為 10 的字符串的快速比較路徑。NotEqualFilter等等,用聲明的 CLR 類型(如string)。會留到後麵的編譯階段去做。String
