【问题标题】:How to Convert Expr<'a -> 'b> to Expression<Func<'a, obj>>如何将 Expr<'a -> 'b> 转换为 Expression<Func<'a, obj>>
【发布时间】:2012-05-19 01:12:01
【问题描述】:

我正在使用带有 .NET 4.5 beta 的 F# 3.0,我正在尝试将 Expr&lt;'a -&gt; 'b&gt; 类型的 F# 引用转换为 LINQ Expression&lt;Func&lt;'a, 'b&gt;&gt;

我发现了几个可以解决此问题的问题,但这些技术似乎不再起作用,可能是由于 F# 3.0 或 .NET 4.5 的变化。

在这两种情况下,当我从任一问题的解决方案中运行代码时,以下操作都会引发异常:

mc.Arguments.[0] :?> LambdaExpression

...其中mcMethodCallExpression。例外是:

System.InvalidCastException:无法将“System.Linq.Expressions.MethodCallExpressionN”类型的对象转换为“System.Linq.Expressions.LambdaExpression”类型。

不,MethodCallExpressionN 末尾的额外“N”不是拼写错误。有人有建议吗?谢谢。

更新

这是一个完整的复制品。事实证明,这段代码在 &lt;@ fun x -&gt; x + 1 @&gt; 这样的表达式上运行良好。我的问题是,就我而言,我需要将Expr&lt;'a -&gt; 'b&gt; 转换为Expr&lt;'a -&gt; obj&gt;,这样我就不必用box 乱扔我的所有lambda 表达式。我通过将原始表达式拼接到这个表达式中来做到这一点:&lt;@ %exp &gt;&gt; box @&gt;。这会生成一个类型正确的对象,但转换为 Expression&lt;Func&lt;'a, obj&gt;&gt; 的代码不再有效。

module Expr =
    open System
    open System.Linq.Expressions
    open Microsoft.FSharp.Quotations
    open Microsoft.FSharp.Linq.QuotationEvaluation

    let rec private translateExpr (linq:Expression) = 
        match linq with
        | :? MethodCallExpression as mc ->
            let le = mc.Arguments.[0] :?> LambdaExpression
            let args, body = translateExpr le.Body
            le.Parameters.[0] :: args, body
        | _ -> [], linq

    let ToFuncExpression (expr:Expr<'a -> 'b>) = 
        let args, body = expr.ToLinqExpression() |> translateExpr 
        Expression.Lambda<Func<'a, 'b>>(body, Array.ofList args) 

let exp = <@ fun x -> x + 1 @>

let r = Expr.ToFuncExpression <@ %exp >> box @>
printfn "%A" r

【问题讨论】:

  • 也许你因为使用无点风格而受到惩罚。如果你改用&lt;@ fun x -&gt; %exp x |&gt; box @&gt; 会发生什么?当您使用无点样式时,您要转换的表达式不是 lambda,而是一个应用程序。
  • @kvb - 这是个好主意,但是当我使用该构造时,它会在%exp 下划线并告诉我“此值不是函数,无法应用”并拒绝编译。跨度>
  • 但是,&lt;@ fun x -&gt; x |&gt; %exp |&gt; box @&gt; 可以编译。不幸的是,当我尝试转换它时,它得到了同样的错误。
  • expr.ToLinqExpression() 现在在 F# 中为 Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToExpression expr

标签: linq f#


【解决方案1】:

您能否发布更完整的示例并包含您尝试转换的 F# 表达式?

我尝试使用最少的示例测试 .NET 4.5 上的行为,它对我有用。这是我所做的:

  • 我创建了新的 F# 3.0 项目并从 F# PowerPack 的 2.0 版本中复制了 Linq.fsLinq.fsi。 (或者在 F# 3.0 的某个地方是否有 3.0 版本的 ToLinqExpression 方法可用?)

  • 我使用了Daniel's earlier answer的代码,调用函数如下:

    let r = toLinq <@ fun x -> x + 1 @>
    printfn "%A" r
    

    这没有引发任何异常,它打印了x =&gt; (x + 1),这对我来说看起来是正确的。

编辑:回答更新后的问题 - 您提到的两个代码示例(我的和 Daniel 的)都假设引用的主体是一个显式构造的函数,因此它们仅适用于特定结构的引用:&lt;@ fun x -&gt; ... @&gt;.

您可以通过在显式构造的函数中使用拼接来解决此问题。以下对我有用:

let exp = <@ fun x -> x + 1 @> 
let r = toLinq <@ fun a -> box ((%exp) a) @> 
printfn "%A" r

这包含 F# 函数的应用程序,因此生成的 Expression 包含对 ToFSharpFunc 的调用(它将委托转换为 F# 函数),然后调用 this。如果您希望标准 .NET 工具可以理解Expression,这可能是一个问题(在这种情况下,您必须对 C# 表达式树进行后处理并删除这些构造)。

【讨论】:

  • 我已经更新了我的问题。事实证明,它可能与 F# 3 或 .NET 4.5 无关,而是我使用了引号拼接的事实。是的,我和你一样将 powerpack Linq 文件复制到了我的项目中。
  • @JoelMueller 谢谢 - 是的,问题是你没有传递包含显式 lambda 的引号。请参阅我修改后的答案。
  • translateExpr 函数仍然在您更新的答案中引发(不同的)错误,所以我将它切换到 Daniel 的代码,现在它可以工作了。谢谢!
  • 不幸的是,AutoMapper(我最终试图与之集成)似乎无法处理任何比属性访问更复杂的 LINQ 表达式。而且,它不会接受Expression&lt;Func&lt;'a, 'b&gt;&gt; 代替Expression&lt;Func&lt;'a, obj&gt;&gt;。所以我不能装箱或投...我需要为此提出一个新问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 2012-09-03
  • 1970-01-01
  • 1970-01-01
  • 2010-10-13
  • 2012-11-12
相关资源
最近更新 更多