繁体   English   中英

F# - 关于传递给C#方法的参数 - 它们是元组还是什么?

[英]F# - On the parameters passed to C# methods - are they tuples or what?

我读了很多次

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

然后我在.NET 4(beta 2)上试验F#和C#interop。 我用以下类创建了一个新的解决方案和一个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 ] (当他的意思是[4; 5; 6] ):

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

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

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<int, int>)
  • 所以它将打印4

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

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

谢谢!

编辑

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

如果你这样做:

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

F#调用Add(Tuple<int, int>)重载。

但是,如果您使用以下方法创建另一个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 -> int -> int ,而不是(int * int) -> int

怎么了?!?

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

它比那更可怕。 语言规范中查看方法重载分辨率strait的描述。

基本上,它所说的是方法调用中的参数实际上并不是一个元组。 它是一个语法元组,意思是逗号分隔的某些东西,但括号是方法调用语法的一部分,逗号也是如此。 这就是为什么,例如, oM(a=1, b=2)不是一个带有两个布尔元组的方法调用,而是两个命名参数。

因此,通常,每个逗号分隔的组件只映射到一个不同的参数。 因此,为什么Add(1, 2)调用Add(int, int)重载, Add((1, 2))调用Add(Tuple<int, int>) 这里没有歧义。

但是,针对您的特定情况启动的特殊情况是:

如果没有命名的实际参数,并且M只有一个候选方法,只接受一个非可选参数,那么将忽略arg到元组形式的分解,并且有一个名为actual arg ,即arg本身。

因此,当您删除除元组之外的所有重载时,括号内的所有内容突然被有效地视为调用中的元组构造函数。 但是如果你有两个重载, Add(int)Add(Tuple<int,int>) ,那么Add(1,2)形式的调用根本就不会解决。

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

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

将打印9而不是

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

会打印.. 4对吗? 注意双重parantheses,标记元组的内部对和标记函数调用的外部对。

这只是编译魔术。

let add a b = a+b 

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

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

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

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM