在C#中访问F#区分联合类型数据的最简单方法是什么?

Dan*_*eny 20 .net c# f# functional-programming

我试图了解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
Run Code Online (Sandbox Code Playgroud)

试图在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);
}
Run Code Online (Sandbox Code Playgroud)

有没有更好的方法呢?即使我必须手动转换(可能存在运行时错误),我希望至少缩短对类型(DannyTest.Result<DannyTest.Request, string>.Failure)的访问.有没有更好的办法?

Tom*_*cek 22

在不支持模式匹配的语言中,使用受歧视的联合会永远不会那么简单.但是,你的Result<'TSuccess, 'TFailure>类型很简单,应该有一些很好的方法从C#中使用它(如果类型更复杂,比如表达式树,那么我可能会建议使用Visitor模式).

其他人已经提到了一些选项 - 如何直接访问值以及如何定义Match方法(如Mauricio的博客文章中所述).我最喜欢的简单DU TryGetXyz方法是定义遵循相同风格的方法Int32.TryParse- 这也保证了C#开发人员熟悉模式.F#定义如下所示:

open System.Runtime.InteropServices

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

type Result<'TSuccess, 'TFailure> with
  member x.TryGetSuccess([<Out>] success:byref<'TSuccess>) =
    match x with
    | Success value -> success <- value; true
    | _ -> false
  member x.TryGetFailure([<Out>] failure:byref<'TFailure>) =
    match x with
    | Failure value -> failure <- value; true
    | _ -> false
Run Code Online (Sandbox Code Playgroud)

这只是添加扩展TryGetSuccess,TryGetFailure并且true当值与案例匹配时返回,并通过out参数返回被区分的并集案例的返回(所有)参数.对于曾经使用过的人来说,使用C#非常简单TryParse:

  int succ;
  string fail;

  if (res.TryGetSuccess(out succ)) {
    Console.WriteLine("Success: {0}", succ);
  }
  else if (res.TryGetFailure(out fail)) {
    Console.WriteLine("Failuere: {0}", fail);
  }
Run Code Online (Sandbox Code Playgroud)

我认为这种模式的熟悉程度是最重要的好处.当您使用F#并将其类型公开给C#开发人员时,您应该以最直接的方式公开它们(C#用户不应该认为F#中定义的类型以任何方式是非标准的).

此外,这为您提供了合理的保证(当正确使用时),您将只访问DU与特定案例匹配时实际可用的值.

  • @Ryan-绝对正确:-) C#7的解决方案要比这好得多! (2认同)

Rya*_*yan 8

使用 C# 7.0 做到这一点的一个非常好的方法是使用 switch 模式匹配,它几乎就像 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;
}
Run Code Online (Sandbox Code Playgroud)

编辑: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()
};
Run Code Online (Sandbox Code Playgroud)

有关详细信息,请参阅https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/


Jam*_*mes 5

Mauricio Scheffer为C#/ F#interop做了一些优秀的帖子,并使用了有和没有核心F#库(或Fsharpx库)的技术,以便能够使用这些概念(在F#中简单化) C#.

http://bugsquash.blogspot.co.uk/2012/03/algebraic-data-type-in​​terop-fc.html

http://bugsquash.blogspot.co.uk/2012/01/encoding-algebraic-data-types-in-c.html

这也许有用:如何在C#中复制F#区别联合类型?


jbt*_*ule 5

您可以使用 C# 类型别名来简化对 C# 文件中 DU 类型的引用。

using DanyTestResult = DannyTest.Result<DannyTest.Request, string>;
Run Code Online (Sandbox Code Playgroud)

由于 C# 8.0 及更高版本具有结构模式匹配,因此可以轻松执行以下操作:

switch (res) {
    case DanyTestResult.Success {Item: var req}:
        Console.WriteLine(req.email);
        Console.WriteLine(req.name);
        break;
    case DanyTestResult.Failure {Item: var msg}:
        Console.WriteLine("Failure");
        Console.WriteLine(msg);
        break;
}
Run Code Online (Sandbox Code Playgroud)

此策略是最简单的,因为它无需修改即可与引用类型 F# DU 一起使用。

如果 F#在代码生成中添加 Deconstruct 方法以实现互操作,则可以进一步减少 C# 语法。DanyTestResult.Success(var req)

如果您的 F# DU 是 struct 样式,则只需对Tag 属性进行模式匹配,无需 type{Tag:DanyTestResult.Tag.Success, SuccessValue:var req}