我多次读过
从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!)
因此,将上述三个引文相加,可以得出结论:
(4, 5)
Add(Tuple<int, int>)
令我惊讶的是,它打印了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#将无法编译,因为Add
为int -> int -> int
,而非(int * int) -> int
。
发生了什么事???
答案 0 :(得分:19)
将参数传递给其他.NET库中的函数时,使用类似“.MethodName(parm1,parm2)”的语法,即参数作为元组传递。
这比那更可怕。请参阅language spec。
中方法重载解析strait的说明它说的基本上是方法调用中的参数不是真正的元组。它是一个语法元组,意思是逗号分隔的某些东西,但括号是方法调用语法的一部分,逗号也是如此。这就是为什么,例如o.M(a=1, b=2)
不是一个带有两个布尔元组的方法调用,而是两个命名参数。
因此,通常,每个逗号分隔的组件只映射到一个不同的参数。因此Add(1, 2)
调用Add(int, int)
重载,Add((1, 2))
调用Add(Tuple<int, int>)
的原因。这里没有歧义。
但是,针对您的特定案例的特殊案例是:
如果没有命名的实际参数,并且
M
中只有一个候选方法,只接受一个非可选参数,则忽略arg
到元组形式的分解,并且一个名为实际arg
的{{1}}本身。
因此当你删除除元组之外的所有重载时,括号内的整个内容突然被有效地视为调用中的元组构造函数。但是,如果你是有两个重载,arg
和Add(int)
,那么Add(Tuple<int,int>)
形式的调用根本无法解决。
答案 1 :(得分:3)
我现在没有安装F#,但在我看来
MyClass.Add(4, 5) |> printf "%d"
将打印9而
MyClass.Add((4, 5)) |> printf "%d"
会打印.. 4对吗?注意双重parantheses,标记元组的内部对和标记函数调用的外部对。
答案 2 :(得分: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
答案 3 :(得分:0)
我不是F#专家,所以我可能有点偏离基础,但我猜测元组的F#概念与BCL System.Tuple
类型无关。元组是F#的核心原则,并且内置于语言中,但C#,VB.NET和大多数其他.NET语言本身不支持元组。由于元组在这些语言中很有用,因此库正在获得对它们的支持。
我想进一步假设F#元组在内存中表示的方式与在C#和朋友中传递参数的方式非常相似。也就是说,它们本质上是其组件的值数组。当将此值数组推送到堆栈以进行方法调用时,它将与将其每个组成组件推送到堆栈上具有相同的效果,就像从C#调用该方法时一样。
因此,您的第二个示例创建了一个F#元组,将其推送到堆栈,然后调用Add
重载,该重载采用元组中包含的类型。
无论如何,这是我的猜测。大概使用F#比我更多,你可能会有更多的洞察力。您还可以通过查看生成的代码Reflector获得更多线索。