2013-09-25 33 views
4

假设有一个F#定义:模式在C#代码匹配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 
*/ 

回答

7

从C#中使用F#歧义的工会是有点不雅,因为它们是如何编译的。

我认为最好的方法是定义一些成员(在F#端),这将简化使用来自C#的类型。有多种选择,但一个我宁愿是定义TryLeftTryRight方法行为类似于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。

另一种替代方法是定义成员Match,该成员需要Func<'a>Func<'b>委托人并使用左/右情况携带的值调用正确的委托。从功能角度来看,这有点更好,但对C#调用者来说可能不那么明显。

1

3.0 spec

联盟类型从其他CLI语言

编译的联合型U具有使用8.5.4编译形式:

  1. 一个CLI静态吸气财产UC为每个空联合案例C.此属性获取代表每个这种情况的单例对象。
  2. 一个CLI嵌套类型UC,用于每个非空union union C.此类型具有实例属性Item1,Item2 ....用于union的每个字段 case或单个实例属性Item如果只有一个领域。 但是,只有一种情况的已编译联合类型不具有 嵌套类型。相反,联合类型本身扮演着 类型的角色。

  3. 一个CLI静态方法U.NewC用于每个非空的联合情况C.此方法为该情况构造一个对象。

  4. 一个CLI实例属性U.IsC,用于每种情况C.此属性对于该情况返回true或false。
  5. 对于每个案例C,一个CLI实例属性U.Tag。该属性提取或计算与案例相对应的整数标记。
  6. 如果U有多个情况,它有一个CLI嵌套类型U.Tags。 U.Tags类型为每种情况增加一个整数字面量,从零开始增加 顺序。

  7. 除了任何 用户定义的属性或方法之外,已编译的联合类型具有实现其自动生成的接口所需的方法。

这些方法和属性不能直接在F#中使用。 但是,这些类型具有面向用户的List.Empty,List.Cons, Option.None和Option.Some属性和/或方法。

编译后的联合类型不能用作另一种CLI的基本类型 语言,因为它至少有一个程序集私有构造函数并且没有公共构造函数。

如果你不能改变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的情况。

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); 
+0

+1这比已接受的答案 – MattDavey

+0

优雅得多,这个解决方案有一些限制。就像,你不能做类似'a.Match(ifLeft:x => x + 1,ifRight:_ => return null);' –