21 Stimmen

F# - Auf die Parameter an C# Methoden übergeben - sind sie Tupel oder was?

Ich habe schon oft gelesen, dass

Aus F# oder einer anderen .NET-Sprache generierte Assemblies sind (fast) ununterscheidbar.

Damals experimentierte ich mit der Interoperabilität von F# und C# unter .NET 4 (Beta 2). Ich habe eine neue Lösung und ein C#-Projekt mit der folgenden Klasse erstellt:

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

Dann, auf einem F#-Projekt, nach dem Verweis auf das C#-Projekt, versuchte ich:

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

So weit, so gut. Dann kam mir ein anderer Satz in den Sinn, den ich schon viele Male gelesen habe (vielleicht in verschiedenen Büchern):

Bei der Übergabe von Argumenten an Funktionen aus anderen .NET-Bibliotheken verwenden Sie eine Syntax wie ".MethodName(parm1, parm2)", d.h. die Parameter werden als Tupel übergeben.

Dazu kommt noch etwas, das ich einmal hier auf SO gelesen habe (aber ich konnte es nicht finden, um es zu verlinken), und zwar zu einer Frage, bei der der Auftraggeber versuchte, ein using wie [ 4, 5, 6 ] (wenn er meinte [4; 5; 6] ) :

"Das Komma ist der 'Tupel-Erzeugungsoperator', für alles andere verwenden Sie das Semikolon."

Dann habe ich meine Klasse wie folgt geändert:

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; }
}

Jetzt habe ich versucht, es auf F# zu verwenden:

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

Wenn man also die drei oben genannten Zitate zusammenzählt, kann man zu dem Schluss kommen, dass:

  • F# erstellt ein Tupel, wenn es sieht (4, 5)
  • Dann wird die Überlast aufgerufen Add(Tuple<int, int>)
  • Es werden also 4

Zu meiner Überraschung, er druckte 9 . Ist das nicht interessant?

Was ist hier wirklich los? Die obigen Zitate und diese praktischen Beobachtungen scheinen im Widerspruch zu stehen. Können Sie die "Argumentation" von F# begründen und vielleicht auf einige MSDN-Dokumente verweisen, wenn möglich?

Danke!

EDITAR

(um weitere Informationen hinzuzufügen (aus der Antwort von Blindy))

Wenn Sie das tun:

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

F# ruft die Add(Tuple<int, int>) Überlastung.

Wenn Sie jedoch ein anderes F#-Projekt (also eine andere Assembly) mit diesem erstellen:

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

Sie können es in C# wie folgt verwenden

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

So weit, so gut. Nun, wenn Sie versuchen, es von F# (von einem anderen Projekt, eine andere Assembly) zu verwenden, müssen Sie tun:

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

Wenn Sie die Argumente als (4, 5) F# wird nicht kompiliert, weil Add es int -> int -> int und nicht (int * int) -> int .

Was ist hier los?!?

20voto

Pavel Minaev Punkte 97251

Bei der Übergabe von Argumenten an Funktionen aus anderen .NET-Bibliotheken verwenden Sie eine Syntax wie ".MethodName(parm1, parm2)", d.h. die Parameter werden als Tupel übergeben.

Es ist noch abscheulicher als das. Siehe die Beschreibung der Auflösung von Methodenüberladungen direkt in der Sprachspezifikation .

Es besagt im Grunde, dass das Argument in einem Methodenaufruf nicht wirklich ein Tupel ist. Es ist ein syntaktische Tupel, d. h. eine durch Komma getrennte Liste von etwas, aber die Klammern sind Teil der Syntax des Methodenaufrufs, ebenso wie die Kommas. Das ist der Grund, warum zum Beispiel, o.M(a=1, b=2) ist kein Methodenaufruf mit einem Tupel aus zwei Booleschen, sondern zwei benannten Argumenten.

Normalerweise wird also jede durch Komma getrennte Komponente einfach einem bestimmten Argument zugeordnet. Daher ist Add(1, 2) ruft auf. Add(int, int) Überlastung, und Add((1, 2)) ruft auf. Add(Tuple<int, int>) . Hier gibt es keine Zweideutigkeit.

Ein Sonderfall, der in Ihrem speziellen Fall zum Tragen kommt, ist jedoch folgender:

Wenn keine tatsächlichen Argumente genannt werden und es nur eine Kandidatenmethode in M , die nur ein nicht-optionales Argument akzeptiert, dann ist die Zerlegung von arg in Tupelform wird ignoriert und es gibt eine benannte tatsächliche arg das ist arg selbst.

Wenn man also alle Überladungen außer der Tupel-Überladung entfernt, wird plötzlich das gesamte Ding innerhalb der Klammern effektiv als Tupel-Konstruktor in einem Aufruf behandelt. Aber wenn Sie z.B. zwei Überladungen haben, Add(int) y Add(Tuple<int,int>) , dann ein Aufruf der Form Add(1,2) überhaupt nicht auflösen würde.

3voto

Blindy Punkte 59463

Ich habe F# im Moment nicht installiert, aber es scheint mir, dass

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

würde 9 drucken, während

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

drucken würde. 4 richtig? Beachten Sie die doppelten Paranthesen, wobei das innere Paar ein Tupel und das äußere Paar den Funktionsaufruf kennzeichnet.

3voto

dan Punkte 9592

Das ist nur Compiler-Magie.

let add a b = a+b 

add kompiliert zu add(a,b) so dass es leicht von C# aus aufgerufen werden kann. Allerdings sehen F#-Programme es immer noch als add a b aufgrund eines Attributs in der AWL.

Beim Aufruf von C#-Funktionen in F# kann es hilfreich sein, sich die C#-Funktion so vorzustellen, als hätte sie nur einen Parameter - ein Tupel, dessen Elemente die richtige Überladung bestimmen. Sie können also schreiben:

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

0voto

P Daddy Punkte 27687

Ich bin kein F#-Experte, so dass ich vielleicht ein bisschen daneben liege, aber ich würde vermuten, dass das F#-Konzept eines Tupels nicht mit der BCL korreliert System.Tuple Typ. Tupel sind ein Kernbestandteil von F# und sind in die Sprache integriert, aber C#, VB.NET und die meisten anderen .NET-Sprachen unterstützen Tupel nicht von Haus aus. Da Tupel in diesen Sprachen nützlich sein können, erhält die Bibliothek Unterstützung für sie.

Ich würde meine Vermutung dahingehend erweitern, dass ein F#-Tupel im Speicher auf die gleiche Weise dargestellt wird, wie Parameter an Methoden in C# und Konsorten übergeben werden. Das heißt, sie sind im Wesentlichen Wert-Arrays ihrer Komponenten. Wenn dieses Werte-Array für einen Methodenaufruf auf den Stack geschoben wird, hätte dies den gleichen Effekt wie das Schieben der einzelnen Komponenten auf den Stack, genau wie beim Aufruf der Methode in C#.

Ihr zweites Beispiel erzeugt also ein F#-Tupel, schiebt es auf den Stack und ruft dann die Add Überladung, die die im Tupel enthaltenen Typen annimmt.

Das ist jedenfalls meine Vermutung. Da Sie vermutlich mehr mit F# gearbeitet haben als ich, haben Sie vielleicht mehr Einblicke als diese. Sie könnten auch zusätzliche Hinweise erhalten, wenn Sie sich den generierten Code ansehen Reflektor .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X