1

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
Alex 75
  • 2,798
  • 1
  • 31
  • 48
  • You can't do it like this. `get_field` is a function, not a quotation. The compiler cannot "look inside" the function and figure out that it's really a property accessor. – Fyodor Soikin May 22 '20 at 23:24
  • Thanks, I think that all that work is unnecessary, I just needed a cast of the property to _obj_. – Alex 75 May 23 '20 at 09:28

0 Answers0