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 はユーザー コードによって処理され
ませ
んでした

私がこれを理解する限り、F# 式は return を返す式ですが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. 次に、コードはメソッド呼び出しの引数を 1 つずつ選択し、それらを引数 0 のラムダにラップし、コンパイルして実行することで、その値を取得しようとします。ライブラリは、引数の値が定数値または囲んでいる環境から取得された値であることに暗黙的に依存しています。

F# ランタイムによって生成される式ツリーの形状 (つまり、パイピングが使用される場合)

c => op_PipeRight(c.Get("x"), ToFSharpFunc(値 => 無視(値)))

これはまだメソッド呼び出し式です (したがって、仮定 1 は依然として正しいでしょう) が、その最初の引数はパラメーター c を使用します。この引数が引数なしでラムダに変換される場合 (() => c.Get("x")) - そのようなラムダのメソッド本体は、いくつかの自由変数 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 に答える