7

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

C# 代码如下所示:

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

其中GetUri方法定义为

public Uri GetUri<T>(Expression<Action<T>> method);

ImagesController.Get定义为

public HttpResponseMessage Get(string id)

在 F# 中,我正在尝试这样做:

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

这会编译,但在运行时会引发此异常:

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

据我了解,F# 表达式是一个返回的表达式unit,但它实际上应该是一个Expression<Action<T>>, 'returning' void

我正在使用 F# 3.0(我认为 - 我正在使用 Visual Studio 2012)。

我该如何解决这个问题?

4

3 回答 3

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())

更新:无法从问题中检查实际库,所以我会尝试模仿我看到的界面。此代码在 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"

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

// C#
linker.GetUri((c : Controller) => c.Get("{file-name}"))
  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)

它可以同时用于 C# 和 F# 端

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

// F#
linker.GetUri(fun (c : Controller) -> c.Get("{filename}"))
于 2013-07-17T14:11:00.270 回答
1

作为 F# 2.0 的解决方法,您可以使用通用返回类型定义自己的“忽略”函数。这显然可以void推断。

let noop _ = Unchecked.defaultof<_>

Href = linker.GetUri<ImagesController>(fun c -> 
    c.Get("{file-name}") |> noop).ToString())
于 2013-07-17T14:28:43.600 回答
1

在这种情况下,我认为您可以在ignore不使用管道的情况下直接调用:

Href = linker.GetUri<ImagesController>(
    fun c -> ignore(c.Get("{file-name}"))).ToString()

更新

鉴于 desco 对 HyprLinkr 行为的诊断,您似乎应该能够使用以下实用程序:

open System
open System.Linq.Expressions

type ActionHelper =
    static member IgnoreResult(e:Expression<Converter<'t,_>>) = 
        Expression.Lambda<Action<'t>>(e.Body, e.Parameters) 

然后你可以做

Href = linker.GetUri<ImagesController>(
    ActionHelper.IgnoreResult(fun c -> c.Get("{file-name}"))).ToString()
于 2013-07-17T15:23:10.110 回答