- MSDN Blogs: LINQ: Building an IQueryable provider series - The Wayward WebLog
- ufcppさんの記事: [雑記] IQueryable の実装
Linq.fs
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
Program.fs
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}"