I have a C# library method that accept Expression<Func<MyClass, object>>
as parameter.
I read this and other similar posts and I finished to use the following syntax to call it from F#.
(I'm using .Net Core 3.1)
open open System.Linq.Expressions
open Microsoft.FSharp.Quotations
let field = <@ System.Func<_,_>(fun t -> t.MyProperty:> obj) @>
let fieldExpression = Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression(field)
fieldExpression is of type Expression<Func<'T,obj>>
and it works.
My problem now is to move that 2 lines of code in a generic function to create the Expression from a F# func, so that my syntax will be more easy/readable and I can reuse it.
Attempt 1
let toFieldExpression (get_field:'T -> 'P) =
<@ System.Func<_,_>(get_field) @>
|> Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression
let fieldExpression = toFieldExpression(fun t -> t.MyProperty)
:> Expression<Func<'T,obj>>
'P
get the type from MyProperty
so I obtain Expression<Func<'T,'P>>
and I'm not able to box the type of 'P into obj.
The cast to Expression<Func<'T,obj>>
doesn't work.
A workaoround of this problem is Attempt 2:
Attempt 2
Pass 'T boxed or upcast to obj (I don't like to make the cast when I use the func but I don't know how to do it in the function)
let toFieldExpression_obj (get_field:'T -> obj) =
<@ System.Func<_,obj>(get_field) @>
|> Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression
let fieldExpression = toFieldExpression(fun t -> t.MyProperty :> obj)
It compiles but fails at runtime where the expression is used probably:
Unable to determine the serialization information for delegateArg0 => value(.$MyContainerClass+fieldExpression@49).Invoke(delegateArg0).
If I check the fieldExpression obtained from the initial code (that works) and from my function (that returns the error), it result to be Expression<Func<'T,obj>>
but with the debug I can see them are different:
working (in line code):
.Lambda #Lambda1<System.Func`2[MyClass,System.Object]>(MyClass $t)
{
(System.Object)$t.When
}
not working (from function):
.Lambda #Lambda1<System.Func`2[MyClass,System.Object]>(MyClass $delegateArg0)
{
.Call .Constant<FSharpFunc`2[MyClass,System.Object]>(<StartupCode$MyNamespace>.$MyContainer+fieldExpression@51).Invoke($delegateArg0)
}
It seems like when I use the function the lambda is wrapped in a delegate.
I tried also this:
let toFieldExpression_obj (get_field:'T -> obj) :Expression<Func<'T,obj>> =
let field:Expr<Func<'T,obj>> = <@ System.Func<'T,obj>(get_field) @>
//LeafExpressionConverter.QuotationToLambdaExpression(field)
let linq = LeafExpressionConverter.QuotationToExpression(field)
let call = linq :?> MethodCallExpression
let lambda = call.Arguments.[0] :?> LambdaExpression
Expression.Lambda<Func<'T, obj>>(lambda.Body, lambda.Parameters)
but it raises an exception on the downcast to MethodCallExpression
.
How accomplish to my goal; moving the creation of the Expression from a func in a function?
To complete the picture, this is the use of the Expression in the C# library:
protected string CreateIndex(Expression<Func<T, object>> field, IndexDirection direction, TimeSpan? TTL = null)
{
if (TTL == TimeSpan.Zero) throw new Exception(TTL_ZERO_ERROR_MESSAGE);
return Collection.Indexes.CreateOne(
new CreateIndexModel<T>(
direction == IndexDirection.Ascending ?
Builders<T>.IndexKeys.Ascending(field) :
Builders<T>.IndexKeys.Descending(field),
new CreateIndexOptions() { ExpireAfter = TTL }
));
}
and I want to replicate the super-simple usage of the C# syntax:
CreateIndex( t => t.MyProperty )
Solution
Turns out all that cumbersome work trying to obtain an Expression is not needed, because:
(from Ian reply here)
One way you can now do this is to take advantage of the fact that F# will perform this conversion automatically when invoking methods on .NET types that expect a
Expression<Func<...>>
.I'm not entirely sure when this got added to the language, but certainly with F# 4, you don't need to explicitly convert F# expressions into LINQ ones.
I read that in the beginning but when fun t -> t.MyProperty
didn't worked and the compiler asks for Expression<Fun<MyClass,obj>>
I implemented the easy inline solution and then all the other complications for generalize it.
The solution was just to cast the property to obj:
base.CreateIndex( (fun t -> t.MyProperty :> obj), IndexDirection.Descernding, Nullable(TTL)) |> ignore