我正在尝试将一些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'
源= System.Core程序
据我了解,F#表达式是一个返回unit
的表达式,但它应该是Expression<Action<T>>
,'返回'void
。
我正在使用F#3.0(我认为 - 我正在使用Visual Studio 2012)。
我该如何解决这个问题?
答案 0 :(得分: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())
UPDATE:无法检查问题中的实际库,所以我会尝试模仿我看到的界面。此代码在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}"))
由F#运行时生成的表达式树的形状(即使用管道时)将是
c =&gt; op_PipeRight(c.Get(“x”),ToFSharpFunc(value =&gt; Ignore(value)))
这仍然是方法调用表达式(假设1仍然是正确的)但它的第一个参数使用参数c。如果这个参数将被转换为没有参数的lambda(()=&gt; c.Get(“x”)) - 那么这个lambda的方法体将引用一些自由变量c - 正是在异常消息中写的内容。 / p>
作为一种更友好的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}"))
答案 1 :(得分:1)
作为F#2.0的变通方法,您可以使用泛型返回类型定义自己的“ignore”函数。这显然允许推断void
。
let noop _ = Unchecked.defaultof<_>
Href = linker.GetUri<ImagesController>(fun c ->
c.Get("{file-name}") |> noop).ToString())
答案 2 :(得分: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()