如何在C#和F#两种语言中尽可能友好地暴露类型?

7
例如,如果我已经用F#编写了一个模块。
module Lib

type A =
    member this.x1 x = ...

let helpa x = ...
let helpb x = ...

type B =
    member this.y1 x = ...

let helpc x = ...

typeA with
    member this.x2 x = ...
typeB with
    member this.y2 x = ...

在F#中,可以通过 open Lib 来很好地使用它。然而,如果我想在C#中使用它(我只对Lib中的类型和成员函数感兴趣),那么每次创建一个类型时都必须使用 new Lib.A(...)。这变得非常繁琐,没有办法省略模块名称。像 Lib.A.C() 这样调用静态方法更加麻烦。
然后我尝试将 module 替换为 namespace,但是每次引入一些帮助函数时,我都必须创建一个新的命名空间。偶尔我可以把所有帮助函数重新排列到一个模块中,但这会导致代码不够易读。
有没有更好的结构呢?
希望我能在C#中使用 Using * = Lib.*

你的C#类顶部有using Lib指令吗?http://msdn.microsoft.com/zh-cn/library/sf0df423(v=vs.80).aspx - Robert Harvey
@RobertHarvey 我认为C#不能使用模块,或者你的意思是类似于 using A = Lib.A 这样的别名吗? - colinfang
请看这里:https://dev59.com/GHRB5IYBdhLWcg3w4bKv - Robert Harvey
1
你可以在这里找到其他有用的建议:https://dev59.com/8eo6XIcBkEYKwwoYQiO7 - pad
2个回答

7

F#在这方面比C#更加灵活,因此我会按照标准方式将其暴露给C#,即将类型封装在命名空间中。我认为以下代码可以兼顾两者的优点:

namespace Lib

type A =
    member this.x1 x = ()

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module A =
  let helpa x = ()
  let helpb x = ()

type B =
    member this.y1 x = ()

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module B =
  let helpb x = ()

type A with
    member this.x2 x = ()
type B with
    member this.y2 x = ()

F#的集合遵循类似的设计。您可以使用[<AutoOpen>][<RequireQualifiedAccess>]属性来进一步控制从F#中使用模块的方式。


丹尼尔,您对如何以友好的方式向 C# 公开 F# 类型(例如 unions、record types 等)有何指导? - Alex Gordon
最近我一直在做一些互操作性的工作,发现如果你使用新的基于表达式的 switch 语法,就像在 f# 中使用 match 一样,f# unions 实际上被很好地公开了。记录只是只读类,所以非常自然。 - Ryan
我上面提到的一个例子:switch (result) { case var checkResult when checkResult.IsOk: HandleOk(checkResult.OkValue); break; case var checkResult when checkResult.IsError: HandleError(checkResult.ErrorValue); break; } - Ryan
我刚在这里发现了一篇很棒的文章:http://connelhooley.uk/blog/2017/04/30/f-sharp-to-c-sharp#cases-with-types - Ryan
除了我的结果类型的原始建议之外,如果联合案例具有内容,您可以使用以下代码: switch (unionInstance) { case Union.FirstType content: HandleFirstType(content.Item); break; case Union.SecondType content: HandleSecondType(content.Item); break; } - Ryan

4

我认为你在回答中已经提到了最好的选择 - 在文件顶部使用 namespace 声明来定义文件(这样,在 C# 中可以只写 using Lib),然后将所有助手函数放入模块中。

与某些类型(例如 A)明显相关联的辅助函数可以放置在名为 A 的模块中(类似于 List 模块中与 List<'T> 类型相关联的 F# 函数)。

这需要更多的工作,因为您需要使用特殊属性标记模块(以避免名称冲突),但它将很容易从 F# 和 C# 中使用(我认为拥有良好的使用体验比在构建库时节省几个按键更重要):

namespace Lib

// Declaration of the 'A' type and helper functions in 'A' module 
type A() =
  member this.x1 x = 10

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module A = 
  let helpa (x:A) = x.x1
  let helpb (x:A) = x.x1

// Declaration of the 'B' type and helper functions in 'B' module 
type B() =
  member this.y1 x = 10

[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
module B = 
  let helpc (x:B) = x.y1

// Member augmentations for easy use from C#
type A with
    member this.x2 x = A.helpa this
type B with
    member this.y2 x = B.helpc this

在C#中,A.helpa变成了AModule.helpa。我必须将这些函数作为类型A的静态成员添加进去。 - MiloDC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接