Last active
March 8, 2025 08:52
-
-
Save matarillo/be0203f7513c75a93790952a325e7f1b to your computer and use it in GitHub Desktop.
LINQ: Building an IQueryable provider series
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module My.Linq | |
open System | |
open System.Collections.Generic | |
open System.Linq | |
open System.Linq.Expressions | |
open System.Reflection | |
// 条件に一致する最初の結果を返す関数 | |
let rec tryFirst (funcs: ('a -> 'b option) list) (input: 'a) = | |
match funcs with | |
| [] -> None | |
| f :: rest -> | |
match f input with | |
| Some result -> Some result | |
| None -> tryFirst rest input | |
// FindIEnumerable関数の実装 | |
let rec findIEnumerable (seqType: Type) : Type option = | |
// チェック関数のリスト | |
let checks = | |
[ | |
// null または string型のチェック | |
fun (t: Type) -> if t = null || t = typeof<string> then Some null else None | |
// 配列型のチェック | |
fun (t: Type) -> | |
if t.IsArray then | |
Some(typeof<IEnumerable<_>>.MakeGenericType(t.GetElementType())) | |
else | |
None | |
// ジェネリック型のチェック | |
fun (t: Type) -> | |
if t.IsGenericType then | |
t.GetGenericArguments() | |
|> Array.tryPick (fun arg -> | |
let ienum = typeof<IEnumerable<_>>.MakeGenericType(arg) | |
if ienum.IsAssignableFrom(t) then Some ienum else None) | |
else | |
None | |
// インターフェースのチェック | |
fun (t: Type) -> | |
let ifaces = t.GetInterfaces() | |
if ifaces <> null && ifaces.Length > 0 then | |
ifaces |> Array.tryPick findIEnumerable | |
else | |
None | |
// 基底クラスのチェック | |
fun (t: Type) -> | |
if t.BaseType <> null && t.BaseType <> typeof<obj> then | |
findIEnumerable t.BaseType | |
else | |
None ] | |
// すべてのチェックを順番に試す | |
tryFirst checks seqType | |
// GetElementType関数の実装 | |
let getElementType (seqType: Type) : Type = | |
match findIEnumerable seqType with | |
| None -> seqType | |
| Some null -> seqType // nullの場合も元の型を返す | |
| Some ienum -> ienum.GetGenericArguments().[0] | |
[<AbstractClass>] | |
type QueryProvider() = | |
abstract member GetQueryText: Expression -> string | |
abstract member Execute: Expression -> obj | |
interface IQueryProvider with | |
member this.CreateQuery(expression: Expression) : IQueryable = | |
let elementType = getElementType expression.Type | |
try | |
let t = typeof<Query<_>>.MakeGenericType(elementType) | |
let args: obj array = [| this; expression |] | |
Activator.CreateInstance(t, args) :?> IQueryable | |
with :? TargetInvocationException as ex -> | |
raise ex.InnerException | |
member this.CreateQuery<'TElement>(expression: Expression) : IQueryable<'TElement> = | |
Query<'TElement>(this, expression) | |
member this.Execute(expression: Expression) : obj = this.Execute expression | |
member this.Execute<'TResult>(expression: Expression) : 'TResult = this.Execute expression :?> 'TResult | |
and Query<'T> = | |
val private _provider: QueryProvider | |
val mutable private _expression: Expression | |
static member private check(expression: Expression) = | |
if typeof<IQueryable<'T>>.IsAssignableFrom(expression.Type) then | |
expression | |
else | |
raise (ArgumentOutOfRangeException("expression")) | |
member this.Provider = this._provider | |
member this.Expression = this._expression | |
new(provider: QueryProvider, expression: Expression) = | |
{ _provider = provider | |
_expression = Query<'T>.check expression } | |
new(provider: QueryProvider) as this = | |
{ _provider = provider | |
_expression = Expression.Empty() } | |
then this._expression <- Query<'T>.check (Expression.Constant(this)) | |
override this.ToString() = | |
this._provider.GetQueryText(this._expression) | |
interface Collections.IEnumerable with | |
member this.GetEnumerator() : Collections.IEnumerator = | |
(this :> IEnumerable<'T>).GetEnumerator() | |
interface IQueryable<'T> with | |
member this.ElementType = typeof<'T> | |
member this.Expression = this._expression | |
member this.Provider = this._provider | |
member this.GetEnumerator() = | |
(this.Provider.Execute(this.Expression) :?> _ seq).GetEnumerator() | |
interface IOrderedQueryable<'T> with |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
open System | |
open System.Linq | |
open System.Linq.Expressions | |
open Microsoft.FSharp.Linq.RuntimeHelpers | |
open My.Linq | |
type TestProvider() = | |
inherit QueryProvider() | |
override _.GetQueryText(exp: Expression) = "" | |
override _.Execute(exp: Expression) = null | |
static member CreateQueryable<'T>() = Query<'T>(TestProvider()) | |
let q1 = TestProvider.CreateQueryable<int>() | |
printfn $"q1: %A{q1.Expression}" | |
let q2 = q1.Where(fun x -> x > 10) | |
printfn $"q2: %A{q2.Expression}" | |
let q3 = q2.OrderBy(fun x -> x) | |
printfn $"q3: %A{q3.Expression}" | |
let q4 = q3.Select(fun x -> x * x) | |
printfn $"q4: %A{q4.Expression}" | |
let q5 = | |
query { | |
for x in TestProvider.CreateQueryable<int>() do | |
where (x > 10) | |
sortBy x | |
select (x * x) | |
} | |
printfn $"q5: %A{q5.Expression}" | |
let expr = <@ Func<int, bool>(fun x -> x > 10) @> | |
let linqExpr = LeafExpressionConverter.QuotationToLambdaExpression(expr) | |
let q2' = q1.Where(linqExpr) | |
printfn $"q2': %A{q2'.Expression}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
MSDN Blogs > The Wayward WebLog > LINQ: Building an IQueryable provider series
https://web.archive.org/web/20100911095114/http://blogs.msdn.com/b/mattwar/archive/2008/11/18/linq-links.aspx