如何在C#中创建F#匿名记录?

12

我可以看到如果我创建一个新的匿名记录,例如:

let myRecord = {| SomeInteger = 5 |}

如果它暴露给C#,那么我就可以使用"."进入它。
var someInteger = myRecord.SomeInteger;

如果我有一个F#函数,比如说:
```fsharp let add x y = x + y ```
该怎么办呢?
let unwrap (record : {| SomeInteger : int |}) = record.SomeInteger

如果这个函数暴露给了C#,我该如何在C#中实例化一个参数并调用它呢?我尝试过直接使用C#的匿名类型放在那里,就像这样。
var unwrapped = unwrap(new { SomeInteger = 5 });

但是这段代码无法编译。我在该功能的RFC文档中看到说,"此功能必须与C#匿名对象(来自C# 3.0)实现兼容性",但没有具体说明哪些方面需要兼容。这个功能是否被支持?

3
“no additional detail is given” 意为“没有提供额外的细节”。RFC在该声明之后直接解释了兼容性要求所指的内容。如果无法利用F#类型推断,就无法创建这种类型的实例,因为它们是匿名类型,没有真正的名称来引用它们,C#无法在方法范围之外使用匿名类型。请注意,翻译过程中应保持原意,并尽可能使语言简洁易懂。 - TeaDrivenDev
2
我建议在ILSpy(或您喜欢的任何类似工具)中打开生成的F#程序集,并查看为myRecord生成的类型。这应该告诉您有关实现兼容性的方式的很多信息。 - madreflection
1
是的,我那里有点含糊,我会更新问题。也许我误解了,但之后的行似乎描述了实现与C#匿名类型具有共同点,而不是它们如何互操作。我不确定类型推断是否是问题所在 - C#编译器可以读取生成的类型并知道如何将匹配的匿名类型或ValueTuple文字编译为它(好吧,我想这不会是F#的特性,因此)。我喜欢这个功能,但如果我们无法使用从C#接收它们的函数,那么“Interop”成为设计目标似乎有点奇怪。 - user2163043
1
我不确定在这种情况下“interop”的范围是什么。问题是:C#中的匿名类型仅基于方法范围内的类型推断创建,并且它们没有名称。要在C#中创建类型的实例,您需要其名称/构造函数,或者它必须是本地匿名类型。对于F#匿名类型,C#的功能将无法超越其 - 它们仍然没有可用于创建它们的名称。您可以从F#函数返回匿名记录,并使用var将其绑定到C#中的变量并访问其字段。 - TeaDrivenDev
1个回答

2
不幸的是,这似乎基本上是不可能的。我们可以使用https://sharplab.io/来查看正在发生的情况以及API将会是什么样子。
您可以查看以下示例:https://sharplab.io/#v2:EYLgxg9gTgpgtADwGwBYA0AbEAzAzgHwFgAoDGAFwAIBbATwCUZIoATSgXkoG99KBlCNRgBJAHbkYAcxhQOlAKyV8AXxJkqAV1EB3KAEMADgB4A5HoB8lABSxmbEN14ChYidNkOzS5QEo5t6BYAOmcRcSkZEiA== 我已将以下 F# 粘贴到其中,让它编译,然后将其反编译为 C# 代码:
let createRecord () = {| SomeInteger = 5 |}
let unwrap (record : {| SomeInteger : int |}) = record.SomeInteger

我们可以看到,F#编译器生成了一个名为<>f__AnonymousType2453178905的类。这个名称是主要问题,因为你不能在 C# 中引用它:/(据我所知)。顺便说一下,有趣的是 SomeInteger 的类型是泛型的,因此你可以将 unwrap 函数写成泛型形式,它仍然会正常工作:
let unwrap<'a> (record : {| SomeInteger : 'a |}) = record.SomeInteger

翻译后的函数如下:
public static <>f__AnonymousType2453178905<int> createRecord()
{
    return new <>f__AnonymousType2453178905<int>(5);
}
public static int unwrap(<>f__AnonymousType2453178905<int> record)
{
    return record.SomeInteger;
}

这意味着:
  • 我建议在公共API中避免使用匿名记录
  • 如果你真的想使用它们,你必须为它们提供工厂函数。
    • 它们可以在所有参数中是通用的,例如let createMyRecord a = {| SomeInteger = a |}将会很好地工作
    • 虽然它可以从C#中使用,但看到参数应该是类型<>f__AnonymousType3239938913<<A>j__TPar, <B>j__TPar, <SomeInteger>j__TPar>并不是很有帮助
  • 如果其他人已经构建了API,现在你必须使用它,你仍然可以使用反射创建实例
    • 你可以通过调用method.GetParameters()[0].ParameterType或者Type.GetType("namespace.typenamenumberOfGenericArgs")
    • 来从MethodInfo中获取类型
    • 类型有一个构造函数,属性按字母顺序排列。你可以使用Activator.CreateInstance(...)来调用它。

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