【问题标题】:F# - On the parameters passed to C# methods - are they tuples or what?F# - 在传递给 C# 方法的参数上 - 它们是元组还是什么?
【发布时间】:2011-01-02 19:23:37
【问题描述】:

我已经读过很多次了

从 F# 或任何其他 .NET 语言生成的程序集(几乎)无法区分。

然后我在 .NET 4(beta 2)上试验 F# 和 C# 互操作。我使用以下类创建了一个新的解决方案和一个 C# 项目:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
}

然后,在一个F#项目上,引用了C#项目后,我尝试了:

MyClsas.Add(4, 5) |> printfn "%d" // prints 9 (no kidding!)

到目前为止一切顺利。然后我读过很多次(可能在不同的书上)的另一句话出现在我的脑海中:

从其他 .NET 库向函数传递参数时,您使用类似“.MethodName(parm1, parm2)”的语法,即,参数作为元组传递。

将其添加到我曾经在 SO 上阅读过的内容中(但无法找到它的链接),关于 OP 试图创建类似 [ 4, 5, 6 ] 的使用的问题(当他的意思是 @ 987654324@):

“逗号是'元组创建运算符',其他一切使用分号。”

然后我将我的课程修改为以下内容:

public class MyClass {
    public static int Add(int a, int b) { return a + b; }
    public static int Add(Tuple<int, int> a) { return a.Item1; }
}

现在我尝试在 F# 上使用它:

MyClass.Add(4, 5) |> printf "%d" // prints ... (keep reading!)

因此,将上面的三个引用相加,可以得出结论:

  • F# 在看到(4, 5) 时会创建一个元组
  • 然后它会调用重载Add(Tuple&lt;int, int&gt;)
  • 所以它会打印 4

令我惊讶的是,它打印了 9。是不是很有趣?

这里到底发生了什么?上述引文和这种实际观察似乎是矛盾的。您能否证明 F# 的“推理”是合理的,如果可能的话,也许可以指向一些 MSDN 文档?

谢谢!

编辑

(添加更多信息(来自 Blindy 的回答))

如果你这样做:

MyClass.Add((4, 5)) |> printfn "%d" // prints 9

F# 调用 Add(Tuple&lt;int, int&gt;) 重载。

但是,如果您使用以下命令创建另一个 F# 项目(因此是不同的程序集):

namespace MyFSharpNamespace
type MyFShapClass = class
    static member Add x y = x + y
    end

你可以像这样在 C# 上使用它

public static void Main(string[] args) {
    MyFSharpNamespace.MyFSharpClass.Add(4, 5);
}

到目前为止一切顺利。现在,当您尝试从 F#(从另一个项目,另一个程序集)使用它时,您必须这样做:

MyFSharpNamespace.MyFSharpClass.Add 4 5 |> printfn "%d"

如果您将参数作为(4, 5) 传递,F# 将无法编译,因为Addint -&gt; int -&gt; int,而不是(int * int) -&gt; int

发生了什么?!?

【问题讨论】:

    标签: c# f# parameters tuples


    【解决方案1】:

    我现在没有安装 F#,但在我看来,

    MyClass.Add(4, 5) |> printf "%d"
    

    将打印 9 而

    MyClass.Add((4, 5)) |> printf "%d"
    

    会打印.. 4 对吗?注意双括号,内对标记元组,外对标记函数调用。

    【讨论】:

    • 是的,我在询问之前进行了测试。但是如果你考虑到这 3 个引用,它仍然很奇怪,不是吗?如果 Add 是 F# 创建的类的静态方法,它接受 2 个参数(并且将用作 C# 中的 .Add(4, 5)),F# 将不会编译 .Add(4, 5)
    • 中间的引用似乎有点奇怪,但最后一个很适合双括号的东西。您是否尝试删除 Add(int,int) 声明以查看它是否解析为元组版本?
    • @Blindy:是的,如果我删除 Add(int, int),那么 F# 会很乐意将 .Add(4, 5) 发送到 C# 的 Add(Tuple&lt;int, int&gt;)
    【解决方案2】:

    我不是 F# 专家,所以我可能有点离题,但我猜 F# 元组的概念与 BCL System.Tuple 类型无关。元组是 F# 的核心原则,并且内置于语言中,但 C#、VB.NET 和大多数其他 .NET 语言本身并不支持元组。由于元组在这些语言中很有用,因此库正在获得对它们的支持。

    我会进一步假设说,F# 元组在内存中的表示方式与将参数传递给 C# 和朋友中的方法的方式非常相似。也就是说,它们本质上是其组件的值数组。当这个值数组被压入堆栈以进行方法调用时,它与将其每个组成组件压入堆栈具有相同的效果,就像从 C# 调用该方法时一样。

    因此,您的第二个示例创建了一个 F# 元组,将其推入堆栈,然后调用采用元组中包含的类型的 Add 重载。

    无论如何,这是我的猜测。大概比我更多地使用 F#,您可能对此有更多的了解。您还可以通过查看生成的代码Reflector 获得更多线索。

    【讨论】:

    • 嗯,很有趣。我将检查在 F# 对 C# 方法的调用和 C# 对 F# 方法的调用的许多不同组合中生成的 IL。谢谢!
    • 这是错误的。 F# 元组 is System.Tuple。其余的都是基于错误前提的错误猜想。
    • @Pavel Minaev:我现在正在进行一些测试,我的实验观察结果似乎与你相矛盾。待我完成测试后,我会发布。
    • printf "%A" ((1, 2).GetType()) 是您需要做的所有检查。也就是说,它当然取决于 .NET 版本 - 在 .NET 4 之前没有 System.Tuple,因此在 3.5 及更低版本上运行的 F# 必须使用自己的类型。但是,由于问题在 C# 代码中提到了Tuple,很明显这只能是 .NET 4。
    【解决方案3】:

    这只是编译器的魔法。

    let add a b = a+b 
    

    add 编译为add(a,b),使其易于从 C# 调用。但是,由于 IL 中的属性,F# 程序仍将其视为 add a b

    当在 F# 中调用 C# 函数时,将 C# 函数视为只有一个参数 - 一个元组可能会有所帮助,它的元素决定了正确的重载。所以你可以写:

    // MyClass.Add(5,3) = 8
    let eight = (5,3) |> MyClass.Add
    

    【讨论】:

    • 有多神奇? “魔法”?还是“更多魔法”?对我来说,这是“更神奇”。
    【解决方案4】:

    从其他 .NET 库向函数传递参数时,您使用类似“.MethodName(parm1, parm2)”的语法,即,参数作为元组传递。

    比这更可怕。参见language spec中对方法重载解析的描述。

    它基本上说的是方法调用中的参数并不是真正的元组。它是一个 syntactic 元组,意思是逗号分隔的列表,但括号是方法调用语法的一部分,逗号也是如此。这就是为什么,例如,o.M(a=1, b=2) 不是带有两个布尔值元组的方法调用,而是两个命名参数。

    因此,通常情况下,每个逗号分隔的组件只映射到一个不同的参数。因此为什么Add(1, 2) 调用Add(int, int) 过载,而Add((1, 2)) 调用Add(Tuple&lt;int, int&gt;)。这里没有歧义。

    但是,针对您的特定情况的一个特殊情况是:

    如果没有命名的实际参数,并且M中只有一个候选方法,只接受一个非可选参数,则将arg分解为元组形式被忽略,并且有一个命名实际@ 987654329@ 是 arg 本身。

    因此,当您删除除元组之外的所有重载时,括号内的整个内容突然在调用中被有效地视为元组构造函数。但是,如果您愿意,例如有两个重载,Add(int)Add(Tuple&lt;int,int&gt;),那么Add(1,2) 形式的调用根本无法解析。

    【讨论】:

      猜你喜欢
      • 2015-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-03
      • 2018-10-04
      • 2019-10-02
      • 2011-07-07
      • 1970-01-01
      相关资源
      最近更新 更多