当方法有返回值时,如何在F#中提供Expression <Action <T >>?

Mar*_*ann 7 f# expression hyprlinkr

我正在尝试将一些C#代码转换为F#.具体来说,我正在尝试使用Hyprlinkr将一些代码转换为F#.

C#代码如下所示:

Href = this.linker.GetUri<ImagesController>(c =>
    c.Get("{file-name}")).ToString()
Run Code Online (Sandbox Code Playgroud)

其中GetUri方法被定义为

public Uri GetUri<T>(Expression<Action<T>> method);
Run Code Online (Sandbox Code Playgroud)

并被ImagesController.Get定义为

public HttpResponseMessage Get(string id)
Run Code Online (Sandbox Code Playgroud)

在F#中,我试图这样做:

Href = linker.GetUri<ImagesController>(
    fun c -> c.Get("{file-name}") |> ignore).ToString())
Run Code Online (Sandbox Code Playgroud)

这个编译,但在运行时抛出此异常:

System.ArgumentException未由用户代码处理
HResult = -2147024809
Message =类型'System.Void'的表达式不能用于返回类型'Microsoft.FSharp.Core.Unit'Source
= System.Core

据我所知,F#表达式是一个返回的表达式unit,但它应该是一个Expression<Action<T>>"返回" void.

我正在使用F#3.0(我想 - 我正在使用Visual Studio 2012).

我该如何解决这个问题?

des*_*sco 3

我的猜测是它应该在 F# 3.1 中修复。这是来自 VS2013 预览版

type T = static member Get(e : System.Linq.Expressions.Expression<System.Action<'T>>) = e
type U = member this.MakeString() = "123"
T.Get(fun (u : U) -> ignore(u.MakeString())) // u => Ignore(u.MakeString())
Run Code Online (Sandbox Code Playgroud)

更新:无法检查问题中的实际库,所以我尝试模仿我看到的界面。此代码在 F# 3.1 中运行良好

open System
open System.Linq.Expressions

type Linker() = 
    member this.GetUri<'T>(action : Expression<Action<'T>>) : string = action.ToString()

type Model() = class end

type Controller() = 
    member this.Get(s : string) = Model()

let linker = Linker()
let text1 = linker.GetUri<Controller>(fun c -> c.Get("x") |> ignore) // c => op_PipeRight(c.Get("x"), ToFSharpFunc(value => Ignore(value)))
let text2 = linker.GetUri<Controller>(fun c -> ignore(c.Get("x"))) // c => Ignore(c.Get("x"))

printfn "Ok"
Run Code Online (Sandbox Code Playgroud)

更新2:我查看了Hyprlinkr的源代码,我想我找到了原因。当前分析表达式树的库代码的实现正在对其形状做出某些假设。尤其:

// C#
linker.GetUri((c : Controller) => c.Get("{file-name}"))
Run Code Online (Sandbox Code Playgroud)
  1. 代码假设表达式树的主体是方法调用表达式(即从控制器调用某些方法)
  2. 然后代码一一挑选方法调用参数,并尝试通过将它们包装到 0 参数 lambda 中来获取其值,然后编译并运行它。库隐式依赖参数值是常量值或从封闭环境捕获的值。

F# 运行时生成的表达式树的形状(即使用管道时)将是

c => op_PipeRight(c.Get("x"), ToFSharpFunc(value => Ignore(value)))

这仍然是方法调用表达式(因此假设 1 仍然正确),但它的第一个参数使用参数 c。如果此参数将转换为不带参数的 lambda (() => c.Get("x")) - 那么此类 lambda 的方法体将引用某个自由变量 c - 正是异常消息中写入的内容。

作为对 F# 更友好的替代方案,我建议为 GetUri 添加额外的重载

public string GetUri<T, R>(Expression<Func<T, R>> e)
Run Code Online (Sandbox Code Playgroud)

它可以在 C# 和 F# 端使用

// C#
linker.GetUri((Controller c) => c.Get("{filename}"))

// F#
linker.GetUri(fun (c : Controller) -> c.Get("{filename}"))
Run Code Online (Sandbox Code Playgroud)