25

C# と F# がどれだけうまく連携できるかを理解しようとしています。F# for Fun & Profit ブログから、識別された共用体型を返す基本的な検証を実行するコードをいくつか引用しました。

type Result<'TSuccess,'TFailure> = 
    | Success of 'TSuccess
    | Failure of 'TFailure

type Request = {name:string; email:string}

let TestValidate input =
    if input.name = "" then Failure "Name must not be blank"
    else Success input

これを C# で使用しようとすると、成功と失敗 (失敗は文字列、成功は再び要求) に対して値にアクセスするために見つけることができる唯一の方法は、大きな厄介なキャストを使用することです (これは多くの入力であり、私が期待する実際の型を入力する必要があります)。推測またはメタデータで利用可能):

var req = new DannyTest.Request("Danny", "fsfs");
var res = FSharpLib.DannyTest.TestValidate(req);

if (res.IsSuccess)
{
    Console.WriteLine("Success");
    var result = ((DannyTest.Result<DannyTest.Request, string>.Success)res).Item;
    // Result is the Request (as returned for Success)
    Console.WriteLine(result.email);
    Console.WriteLine(result.name);
}

if (res.IsFailure)
{
    Console.WriteLine("Failure");
    var result = ((DannyTest.Result<DannyTest.Request, string>.Failure)res).Item;
    // Result is a string (as returned for Failure)
    Console.WriteLine(result);
}

これを行うより良い方法はありますか?手動でキャストする必要がある場合でも (実行時エラーの可能性があります)、少なくとも型 ( ) へのアクセスを短縮したいと考えていますDannyTest.Result<DannyTest.Request, string>.Failure。より良い方法はありますか?

4

7 に答える 7

8

C# 7.0 でこれを行うための非常に優れた方法は、スイッチ パターン マッチングを使用することです。これはすべて F# の一致に似ています。

var result = someFSharpClass.SomeFSharpResultReturningMethod()

switch (result)
{
    case var checkResult when checkResult.IsOk:
       HandleOk(checkResult.ResultValue);
       break;
    case var checkResult when checkResult.IsError:
       HandleError(checkResult.ErrorValue);
       break;
}

編集: C# 8.0 が間もなく登場し、switch 式が導入される予定です。まだ試していませんが、次のようなことができるようになると期待しています。

var returnValue = result switch 
{
    var checkResult when checkResult.IsOk:     => HandleOk(checkResult.ResultValue),
    var checkResult when checkResult.IsError   => HandleError(checkResult.ErrorValue),
    _                                          => throw new UnknownResultException()
};

詳細については、 https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/を参照してください。

于 2018-03-23T14:39:11.277 に答える
3

これはどう?上記の @Mauricio Scheffer のコメントと FSharpx のCSharpCompatコードに触発されています。

C#:

MyUnion u = CallIntoFSharpCode();
string s = u.Match(
  ifFoo: () => "Foo!",
  ifBar: (b) => $"Bar {b}!");

F#:

  type MyUnion =
    | Foo
    | Bar of int
  with
    member x.Match (ifFoo: System.Func<_>, ifBar: System.Func<_,_>) =
      match x with
      | Foo -> ifFoo.Invoke()
      | Bar b -> ifBar.Invoke(b)

これについて私が最も気に入っているのは、実行時エラーの可能性がなくなることです。コードに偽のデフォルト ケースがなくなり、F# の型が変更された場合 (例: ケースの追加)、C# コードはコンパイルに失敗します。

于 2019-08-20T22:57:59.140 に答える
2

おそらく、これを実現する最も簡単な方法の 1 つは、一連の拡張メソッドを作成することです。

public static Result<Request, string>.Success AsSuccess(this Result<Request, string> res) {
    return (Result<Request, string>.Success)res;
}

// And then use it
var successData = res.AsSuccess().Item;

この記事には良い洞察が含まれています。見積もり:

このアプローチの利点は 2 つあります。

  • コードで型に明示的に名前を付ける必要がなくなるため、型推論の利点が得られます。
  • これで、任意の値に対して使用できるよう.になり、Intellisense を使用して適切な方法を見つけることができます。

ここでの唯一の欠点は、インターフェイスを変更すると、拡張メソッドのリファクタリングが必要になることです。

プロジェクトにそのようなクラスが多すぎる場合は、ReSharper などのツールを使用することを検討してください。このためのコード生成をセットアップすることはそれほど難しくないようです。

于 2013-06-22T21:02:42.640 に答える
0

次のメソッドを使用して、F# ライブラリから C# ホストへのユニオンを相互運用しています。これにより、リフレクションの使用により実行時間が長くなる可能性があり、各ユニオンケースの正しいジェネリック型を処理するために、おそらく単体テストによってチェックする必要があります。

  1. F# 側
type Command = 
     | First of FirstCommand
     | Second of SecondCommand * int

module Extentions =
    let private getFromUnionObj value =
        match value.GetType() with 
        | x when FSharpType.IsUnion x -> 
            let (_, objects) = FSharpValue.GetUnionFields(value, x)
            objects                        
        | _ -> failwithf "Can't parse union"

    let getFromUnion<'r> value =    
        let x = value |> getFromUnionObj
        (x.[0] :?> 'r)

    let getFromUnion2<'r1,'r2> value =    
        let x = value |> getFromUnionObj
        (x.[0] :?> 'r1, x.[1] :? 'r2)
  1. C#側
        public static void Handle(Command command)
        {
            switch (command)
            {
                case var c when c.IsFirstCommand:
                    var data = Extentions.getFromUnion<FirstCommand>(change);
                    // Handler for case
                    break;
                case var c when c.IsSecondCommand:
                    var data2 = Extentions.getFromUnion2<SecondCommand, int>(change);
                    // Handler for case
                    break;
            }
        }
于 2018-05-05T10:42:43.470 に答える