【问题标题】:How do you create F# anonymous records in C#?如何在 C# 中创建 F# 匿名记录?
【发布时间】:2019-04-04 20:14:42
【问题描述】:

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

let myRecord = {| SomeInteger = 5 |}

如果它暴露在 C# 中,那么我可以用

var someInteger = myRecord.SomeInteger;

反过来呢,如果我有一个 F# 函数,说:

let unwrap (record : {| SomeInteger : int |}) = record.SomeInteger

它暴露给 C#,我如何从 C# 实例化这个函数的参数并调用它?我天真地尝试在那里放置一个 C# 匿名类型,即。

var unwrapped = unwrap(new { SomeInteger = 5 });

但这没有编译。我在RFC for the feature 中注意到,“该功能必须实现与 C# 匿名对象的兼容性(来自 C# 3.0)”,但没有具体提到具体采用何种方式。支持吗?

【问题讨论】:

  • “没有给出额外的细节”? RFC 在该声明之后的行中解释了兼容性要求直接指的内容。如果您不能利用 F# 类型推断,则无法创建此类类型的实例,因为作为 anonymous 类型,它们没有可以引用它们的真实名称,而 C# 没有在方法范围之外使用匿名类型的方法。
  • 我建议在 ILSpy(或您喜欢的任何此类工具)中打开生成的 F# 构建程序集,并查看为 myRecord 生成的类型。这应该会告诉您很多关于实现兼容性的方式。
  • 是的,我有点含糊,我会更新这个问题。也许我理解错了,但在我看来,后面的几行似乎描述了实现与 C# 匿名类型的共同点,而不是它们如何互操作。我不确定类型推断是否是这里的问题 - C# 编译器可以读取生成的类型并知道如何编译匹配的匿名类型或 ValueTuple 文字(好的,所以我猜这不是 F# 功能本身待定)。我喜欢这个功能,但如果我们不能使用从 C# 接收它们的函数,那么将“互操作”作为设计目标似乎有点奇怪。
  • 我不确定在这种情况下“互操作”的范围是什么。问题是:C# 中的匿名类型仅基于方法范围内的类型推断创建,并且它们没有名称。要在 C# 中创建类型的实例,您需要它的名称/构造函数,或者它必须是本地匿名类型。对于匿名 F# 类型,C# 的功能将不会超过这一点——它们仍然没有您可以用来创建它们的名称。您可以做的是从 F# 函数返回一条匿名记录,然后使用 var 将其绑定到 C# 中的变量并访问其字段。

标签: c# f# record


【解决方案1】:

不幸的是,这似乎基本上是不可能的。我们可以使用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# :/ (AFAIK) 中引用它。顺便说一句,有趣的是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# 中使用,但看到参数应该是 &lt;&gt;f__AnonymousType3239938913&lt;&lt;A&gt;j__TPar, &lt;B&gt;j__TPar, &lt;SomeInteger&gt;j__TPar&gt; 类型并没有多大帮助
  • 如果其他人已经构建了 API,现在您必须使用,您仍然可以使用反射创建实例
    • 您可以通过调用method.GetParameters()[0].ParameterTypeType.GetType("namespace.typenamenumberOfGenericArgs"从MethodInfo 获取类型)
    • 该类型有一个构造函数,属性按字母顺序排列。您可以使用Activator.CreateInstance(...) 调用它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-05
    • 2010-12-26
    • 2021-12-03
    相关资源
    最近更新 更多