代码之家  ›  专栏  ›  技术社区  ›  Bruno Reis

在传递给c方法的参数上,它们是元组还是什么?

  •  19
  • Bruno Reis  · 技术社区  · 16 年前

    我读过很多次了

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

    然后我在.NET 4(测试版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)”的语法,即参数作为元组传递。

    再加上我在这里读过的一些东西,所以(但找不到链接到),关于一个问题,操作人员试图创建一个 [ 4, 5, 6 ] (当他指的是 [4; 5; 6] ):

    逗号是“tuple creating operator”,对于其他所有内容,都使用分号。

    然后我把我的班级改为:

    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文档?

    谢谢!

    编辑

    (添加更多信息(来自布林迪的答案)

    如果你这样做:

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

    F称之为 添加(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 .

    发生什么事了????

    4 回复  |  直到 16 年前
        1
  •  19
  •   Pavel Minaev    16 年前

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

    比这更可怕。方法过载分辨海峡说明见 language spec .

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

    所以,通常情况下,每个逗号分隔的组件都映射到一个不同的参数。因此为什么 Add(1, 2) 电话 Add(int, int) 过载,以及 Add((1, 2)) 电话 Add(Tuple<int, int>) . 这里没有歧义。

    但是,针对您的特定案例的一个特殊案例是:

    如果没有指定的实际参数,并且中只有一个候选方法 M ,只接受一个非可选参数,然后分解 arg to tuple形式被忽略,并且有一个名为actual的形式 精氨酸 哪个是 精氨酸 本身。

    因此,当移除除元组外的所有重载时,突然括号内的整个内容在调用中被有效地视为元组构造函数。但是如果你有两个超负荷, Add(int) 和 Add(Tuple<int,int>) ,然后调用窗体 Add(1,2) 根本解决不了。

        2
  •  3
  •   Blindy    16 年前

    我现在没有安装F,但我觉得

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

    将打印9,而

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

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

        3
  •  3
  •   dan    16 年前

    这只是编译器的魔力。

    let add a b = a+b 
    

    add 编译到 add(a,b) ,方便从C_呼叫。然而,F程序仍然将其视为 add a b 因为IL中有一个属性。

    当在f_中调用c函数时,将c_函数看作只有一个参数——一个元组,它的元素决定了正确的重载。所以你可以写:

    // MyClass.Add(5,3) = 8
    let eight = (5,3) |> MyClass.Add
    
        4
  •  0
  •   P Daddy    16 年前

    我不是一个F专家,所以我可能有点离题了,但我猜一个元组的F概念与BCL不相关。 System.Tuple 类型。tuples是f_的核心原则,并内置于该语言中,但c_、vb.net和大多数其他.NET语言本身不支持tuples。因为元组在这些语言中是有用的,所以库正在获得对它们的支持。

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

    所以第二个例子创建了一个f元组,将它推到堆栈上,然后调用 Add 采用元组中包含的类型的重载。

    不管怎样,这是我的猜测。你大概比我更喜欢用F,也许你对此有更深入的了解。您还可以通过查看生成的代码获得其他线索。 Reflector .