4

F#次の定義があるとします。

type Either<'a,'b> = | Left of 'a | Right of 'b

let f (i : int) : Either<int, string> =
    if i > 0
        then Left i
        else Right "nothing"

関数fC#コードで使用されます:

var a = Library.f(5);

結果値aをデータ コンストラクターのパターン マッチにするにはどうすればよいですか? 何かのようなもの:

/*
(if a is Left x)
    do something with x
(if a is Right y)
    do something with y
*/
4

3 に答える 3

7

C# から F# の識別共用体を使用するのは、コンパイル方法が原因で少し洗練されていません。

最善の方法は、C# の型の使用を簡素化するいくつかのメンバーを (F# 側で) 定義することだと思います。複数のオプションがありますが、私が好むのは、 と と同様に動作するメソッドを定義することですTryLeft(TryRightしたがってInt32.TryParse、F# API を使用する C# 開発者にはなじみがあるはずです)。

open System.Runtime.InteropServices

type Either<'a,'b> = 
  | Left of 'a 
  | Right of 'b
  member x.TryLeft([<Out>] a:byref<'a>) =
    match x with Left v -> a <- v; true | _ -> false
  member x.TryRight([<Out>] b:byref<'b>) =
    match x with Right v -> b <- v; true | _ -> false

次に、次のように C# の型を使用できます。

int a;
string s;
if (v.TryLeft(out a)) Console.WriteLine("Number: {0}", a);
else if (v.TryRight(out s)) Console.WriteLine("String: {0}", s);

これを行うと F# の安全性がいくらか失われますが、これはパターン マッチングのない言語では当然のことです。しかし、幸いなことに、.NET に詳しい人なら誰でも F# で実装された API を使用できるはずです。

もう 1 つの方法は、左右のケースによって運ばれる値を使用して、右側のデリゲートをMatch取りFunc<'a>、デリゲートして呼び出すメンバーを定義することです。Func<'b>これは、機能の観点からは少し優れていますが、C# の呼び出し元にはわかりにくいかもしれません。

于 2013-09-25T18:29:51.003 に答える
6

Match各シナリオで実行するデリゲートを取るメンバーを定義します。F# では、次のようにします (ただし、必要に応じて、C# 拡張メソッドで同等のことを行うこともできます)。

type Either<'a,'b> = | Left of 'a | Right of 'b
with
    member this.Match<'t>(ifLeft:System.Func<'a,'t>, ifRight:System.Func<'b,'t>) =
        match this with
        | Left a -> ifLeft.Invoke a
        | Right b -> ifRight.Invoke b

これで、C# で次のようなことができるはずです。

var result = a.Match(ifLeft: x => x + 1, ifRight: y => 2 * y);
于 2013-09-25T21:00:43.473 に答える
1

3.0仕様から:

8.5.4 他の CLI 言語から使用するための共用体型のコンパイル済み形式

コンパイルされた共用体型 U には次のものがあります。

  1. null ユニオン ケース C ごとに 1 つの CLI 静的ゲッター プロパティ UC。このプロパティは、そのような各ケースを表すシングルトン オブジェクトを取得します。
  2. null 以外のユニオン ケース C ごとに 1 つの CLI ネスト型 UC。この型には、ユニオン ケースの各フィールドのインスタンス プロパティ Item1、Item2....、またはフィールドが 1 つしかない場合は単一のインスタンス プロパティ Item があります。ただし、ケースが 1 つしかないコンパイル済み共用体型には、ネストされた型はありません。代わりに、ユニオン型自体がケース型の役割を果たします。

  3. null 以外のユニオン ケース C ごとに 1 つの CLI 静的メソッド U.NewC。このメソッドは、そのケースのオブジェクトを構築します。

  4. ケース C ごとに 1 つの CLI インスタンス プロパティ U.IsC。このプロパティは、ケースに対して true または false を返します。
  5. ケース C ごとに 1 つの CLI インスタンス プロパティ U.Tag。このプロパティは、ケースに対応する整数タグを取得または計算します。
  6. U に複数のケースがある場合、1 つの CLI ネスト型 U.Tags があります。U.Tags 型には、ケースごとに 1 つの整数リテラルが含まれており、ゼロから昇順になっています。

  7. コンパイルされた共用体型には、ユーザー定義のプロパティまたはメソッドに加えて、自動生成されたインターフェイスを実装するために必要なメソッドがあります。

これらのメソッドとプロパティは、F# から直接使用することはできません。ただし、これらの型には、ユーザー向けの List.Empty、List.Cons、Option.None、および Option.Some のプロパティやメソッドがあります。

コンパイルされた共用体型は、別の CLI 言語で基本型として使用することはできません。これは、少なくとも 1 つのアセンブリ プライベート コンストラクターがあり、パブリック コンストラクターがないためです。

F# API を変更できない場合は、上記のポイント 2 と 4 を使用して、次のようにすることができます。

C#

class Program
{
    static void Main(string[] args)
    {
        PrintToConsole("5");
        PrintToConsole("test");
    }

    static void PrintToConsole(string value)
    {
        var result = test.getResult(value);
        if (result.IsIntValue) Console.WriteLine("Is Int: " + ((test.DUForCSharp.IntValue)result).Item);
        else Console.WriteLine("Is Not Int: " + ((test.DUForCSharp.StringValue)result).Item);
    }
}

F#

namespace Library1

module test =

    open System

    type DUForCSharp =
    | IntValue of int
    | StringValue of string

    let getResult x =
        match Int32.TryParse x with
        | true, value -> IntValue(value)
        | _ -> StringValue(x)

このソリューションは、タプル内の各項目に新しいプロパティを作成することで、タプル DU のケースも処理できるという点で便利です。

于 2013-09-25T18:59:01.817 に答える