Parameter und Argumente

In diesem Thema wird beschrieben, wie die Sprache die Definition von Parametern und die Übergabe von Argumenten an Funktionen, Methoden und Eigenschaften unterstützt. Es enthält Informationen zur Übergabe durch Verweis und zur Definition und Verwendung von Methoden, die eine variable Anzahl von Argumenten akzeptieren können.

Parameter und Argumente

Mit dem Begriff Parameter werden die Namen für Werte beschrieben, deren Angabe erwartet wird. Der Begriff Argument bezieht sich auf die für jeden Parameter angegebenen Werte.

Parameter können in Tupel- oder Curry-Form oder in Kombination angegeben werden. Sie können Argumente unter Verwendung eines expliziten Parameternamens übergeben. Parameter von Methoden können als optional angegeben und mit einem Standardwert versehen werden.

Parametermuster

Parameter, die an Funktionen und Methoden übergeben werden, sind im Allgemeinen durch Leerzeichen getrennte Muster. Das bedeutet, dass im Prinzip jedes der in Übereinstimmungsausdrücken beschriebenen Muster in einer Parameterliste für eine Funktion oder einen Member verwendet werden kann.

Methoden übergeben ihre Argumente in der Regel in Form von Tupeln. Dadurch wird aus Sicht anderer .NET-Sprachen ein klareres Ergebnis erzielt, da die Tupelform der Vorgehensweise beim Übergeben von Argumenten in .NET-Methoden entspricht.

Die Curry-Form wird am häufigsten mit Funktionen verwendet, die mithilfe von let-Bindungen erstellt wurden.

Der folgende Pseudocode zeigt Beispiele für Tupel- und Curry-Argumente.

// Tuple form.
member this.SomeMethod(param1, param2) = ...
// Curried form.
let function1 param1 param2 = ...

Kombinierte Formen sind möglich, wenn einige Argumente in Tupeln enthalten sind und andere nicht.

let function2 param1 (param2a, param2b) param3 = ...

In Parameterlisten sind auch andere Muster möglich, aber wenn das Parametermuster nicht mit allen möglichen Eingaben übereinstimmt, kann es zur Laufzeit zu einer unvollständigen Übereinstimmung kommen. Die Ausnahme MatchFailureException wird generiert, wenn der Wert eines Arguments nicht mit den in der Parameterliste angegebenen Mustern übereinstimmt. Der Compiler gibt eine Warnung aus, wenn ein Parametermuster unvollständige Übereinstimmungen zulässt. Mindestens ein anderes Muster ist häufig für Parameterlisten nützlich: das Platzhaltermuster. Sie nutzen das Platzhaltermuster in einer Parameterliste, wenn Sie einfach alle Argumente ignorieren möchten, die angegeben werden. Der folgende Code veranschaulicht das Platzhaltermuster in einer Argumentliste.

let makeList _ = [ for i in 1 .. 100 -> i * i ]
// The arguments 100 and 200 are ignored.
let list1 = makeList 100
let list2 = makeList 200

Das Platzhaltermuster kann immer dann nützlich sein, wenn Sie die übergebenen Argumente nicht benötigen, z. B. im Haupteinstiegspunkt eines Programms, wenn Sie nicht an den Befehlszeilenargumenten interessiert sind, die normalerweise als Zeichenfolge übergeben werden (siehe den folgenden Code).

[<EntryPoint>]
let main _ =
    printfn "Entry point!"
    0

Andere mitunter in Argumenten vorkommende Muster sind das as-Muster sowie Bezeichnermuster, die diskriminierten Vereinigungsmengen und aktiven Mustern zugeordnet sind. Sie können das Muster der diskriminierten Vereinigungsmenge im Einzelfall wie folgt nutzen.

type Slice = Slice of int * int * string

let GetSubstring1 (Slice(p0, p1, text)) =
    printfn "Data begins at %d and ends at %d in string %s" p0 p1 text
    text[p0..p1]

let substring = GetSubstring1 (Slice(0, 4, "Et tu, Brute?"))
printfn "Substring: %s" substring

Die Ausgabe lautet wie folgt.

Data begins at 0 and ends at 4 in string Et tu, Brute?
Et tu

Aktive Muster können als Parameter nützlich sein, z. B. wenn Sie wie im folgenden Beispiel ein Argument in ein gewünschtes Format transformieren möchten:

type Point = { x : float; y : float }

let (| Polar |) { x = x; y = y} =
    ( sqrt (x*x + y*y), System.Math.Atan (y/ x) )

let radius (Polar(r, _)) = r
let angle (Polar(_, theta)) = theta

Mit dem Muster as können Sie, wie in der folgenden Codezeile gezeigt, einen übereinstimmenden Wert als lokalen Wert speichern.

let GetSubstring2 (Slice(p0, p1, text) as s) = s

Ein weiteres gelegentlich verwendetes Muster ist eine Funktion, die das letzte Argument unbenannt lässt, indem sie als Körper der Funktion einen Lambdaausdruck bereitstellt, der sofort einen Mustervergleich mit dem impliziten Argument durchführt. Ein Beispiel hierfür ist die folgende Codezeile.

let isNil = function [] -> true | _::_ -> false

Dieser Code definiert eine Funktion, die eine generische Liste akzeptiert und bei leerer Liste true und andernfalls false zurückgibt. Solche Techniken können das Lesen von Code erschweren.

Gelegentlich sind Muster mit unvollständigen Übereinstimmungen nützlich. Wenn Sie z. B. wissen, dass die Listen in Ihrem Programm nur drei Elemente enthalten, können Sie in einer Parameterliste ein Muster wie das folgende verwenden.

let sum [a; b; c;] = a + b + c

Muster mit unvollständigen Übereinstimmungen sind am besten für die schnelle Erstellung von Prototypen und andere temporäre Zwecke geeignet. Der Compiler gibt bei solchem Code eine Warnung aus. Diese Muster können nicht den allgemeinen Fall aller möglichen Eingaben abdecken und sind daher nicht für Komponenten-APIs geeignet.

Benannte Argumente

Argumente für Methoden können durch die Position in einer durch Trennzeichen getrennten Argumentliste angegeben werden. Sie können auch explizit an eine Methode übergeben werden, indem Sie den Namen, gefolgt von einem Gleichheitszeichen und dem zu übergebenden Wert, angeben. Bei Angabe durch Bereitstellung des Namens können sie in einer anderen Reihenfolge als in der Deklaration aufgeführt sein.

Benannte Argumente können den Code lesbarer gestalten und eine bessere Anpassung an bestimmte Arten von Änderungen in der API ermöglichen, wie z. B. eine Neuanordnung von Methodenparametern.

Benannte Argumente sind nur für Methoden erlaubt, nicht für an let gebundene Funktionen, Funktionswerte oder Lambdaausdrücke.

Das folgende Codebeispiel veranschaulicht benannte Argumente.

type SpeedingTicket() =
    member this.GetMPHOver(speed: int, limit: int) = speed - limit

let CalculateFine (ticket : SpeedingTicket) =
    let delta = ticket.GetMPHOver(limit = 55, speed = 70)
    if delta < 20 then 50.0 else 100.0

let ticket1 : SpeedingTicket = SpeedingTicket()
printfn "%f" (CalculateFine ticket1)

In einem Aufruf eines Klassenkonstruktors können Sie die Werte von Eigenschaften der Klasse festlegen, indem Sie eine ähnliche Syntax wie für benannte Argumente wählen. Das folgende Beispiel verdeutlicht diese Syntax.

 type Account() =
    let mutable balance = 0.0
    let mutable number = 0
    let mutable firstName = ""
    let mutable lastName = ""
    member this.AccountNumber
       with get() = number
       and set(value) = number <- value
    member this.FirstName
       with get() = firstName
       and set(value) = firstName <- value
    member this.LastName
       with get() = lastName
       and set(value) = lastName <- value
    member this.Balance
       with get() = balance
       and set(value) = balance <- value
    member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
    member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(AccountNumber=8782108,
                           FirstName="Darren", LastName="Parker",
                           Balance=1543.33)

Weitere Informationen finden Sie unter Konstruktoren (F#).

Die gleiche Technik, die zum Aufrufen von Eigenschaftssettern gedacht ist, gilt auch für jede Objektrückgabemethode (z. B. Factorymethoden):

type Widget() =
    member val Width = 1 with get,set
    member val Height = 1 with get,set

type WidgetFactory =
    static member MakeNewWidget() =
         new Widget()
    static member AdjustWidget(w: Widget) =
         w
let w = WidgetFactory.MakeNewWidget(Width=10)
w.Width // = 10
w.Height // = 1
WidgetFactory.AdjustWidget(w, Height=10)
w.Height // = 10

Beachten Sie, dass diese Member jede beliebige Arbeit ausführen können. Die Syntax ist praktisch eine Abkürzung für den Aufruf von Eigenschaftssettern, bevor der endgültige Wert zurückgegeben wird.

Optionale Parameter

Sie können einen optionalen Parameter für eine Methode angeben, indem Sie dem Parameternamen ein Fragezeichen voranstellen. Optionale Parameter werden als F#-Optionstyp interpretiert, sodass Sie sie auf die übliche Weise der Abfrage von Optionstypen abfragen können, indem Sie einen match-Ausdruck mit Some und None verwenden. Optionale Parameter sind nur für Member und nicht für Funktionen zulässig, die mithilfe von let-Bindungen erstellt wurden.

Sie können vorhandene optionale Werte anhand des Parameternamens an die Methode übergeben, z. B. ?arg=None oder ?arg=Some(3) oder ?arg=arg. Dies kann beim Erstellen einer Methode nützlich sein, die optionale Argumente an eine andere Methode übergibt.

Sie können auch mit der Funktion defaultArg einen Standardwert für ein optionales Argument festlegen. Die defaultArg-Funktion verwendet den optionalen Parameter als erstes und den Standardwert als zweites Argument.

Das folgende Beispiel veranschaulicht optionale Parameter.

type DuplexType =
    | Full
    | Half

type Connection(?rate0 : int, ?duplex0 : DuplexType, ?parity0 : bool) =
    let duplex = defaultArg duplex0 Full
    let parity = defaultArg parity0 false
    let mutable rate = match rate0 with
                        | Some rate1 -> rate1
                        | None -> match duplex with
                                  | Full -> 9600
                                  | Half -> 4800
    do printfn "Baud Rate: %d Duplex: %A Parity: %b" rate duplex parity

let conn1 = Connection(duplex0 = Full)
let conn2 = Connection(duplex0 = Half)
let conn3 = Connection(300, Half, true)
let conn4 = Connection(?duplex0 = None)
let conn5 = Connection(?duplex0 = Some(Full))

let optionalDuplexValue : option<DuplexType> = Some(Half)
let conn6 = Connection(?duplex0 = optionalDuplexValue)

Die Ausgabe lautet wie folgt.

Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false
Baud Rate: 300 Duplex: Half Parity: true
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 9600 Duplex: Full Parity: false
Baud Rate: 4800 Duplex: Half Parity: false

Für die Zwecke der Interoperabilität mit C# und Visual Basic können Sie die Attribute [<Optional; DefaultParameterValue<(...)>] in F# verwenden, sodass Aufrufer ein Argument als optional erkennen. Dies ist gleichbedeutend mit der Definition des Arguments als optional in C# wie in MyMethod(int i = 3).

open System
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue("Hello world")>] message) =
        printfn $"{message}"

Sie können auch ein neues Objekt als Standardparameterwert angeben. Beispielsweise kann der Member Foo stattdessen ein optionales CancellationToken als Eingabe enthalten:

open System.Threading
open System.Runtime.InteropServices
type C =
    static member Foo([<Optional; DefaultParameterValue(CancellationToken())>] ct: CancellationToken) =
        printfn $"{ct}"

Der als Argument für DefaultParameterValue angegebene Wert muss dem Typ des Parameters entsprechen. Beispielsweise wird Folgendes nicht unterstützt:

type C =
    static member Wrong([<Optional; DefaultParameterValue("string")>] i:int) = ()

In diesem Fall generiert der Compiler eine Warnung und ignoriert beide Attribute vollständig. Beachten Sie, dass der Standardwert null mit Typanmerkungen versehen werden muss, da andernfalls der Compiler den falschen Typ ableitet, d. h. [<Optional; DefaultParameterValue(null:obj)>] o:obj.

Übergeben durch Verweis

Das Übergeben eines F#-Werts durch Verweis umfasst byrefs, bei denen es sich um verwaltete Zeigertypen handelt. Es folgt eine Anleitung zum zu verwendenden Typ:

  • Verwenden Sie inref<'T>, wenn Sie den Zeiger nur lesen müssen.
  • Verwenden Sie outref<'T>, wenn Sie nur in den Zeiger schreiben müssen.
  • Verwenden Sie byref<'T>, wenn Sie den Zeiger sowohl lesen als auch in den Zeiger schreiben müssen.
let example1 (x: inref<int>) = printfn $"It's %d{x}"

let example2 (x: outref<int>) = x <- x + 1

let example3 (x: byref<int>) =
    printfn $"It's %d{x}"
    x <- x + 1

let test () =
    // No need to make it mutable, since it's read-only
    let x = 1
    example1 &x

    // Needs to be mutable, since we write to it
    let mutable y = 2
    example2 &y
    example3 &y // Now 'y' is 3

Da der Parameter ein Zeiger und der Wert veränderlich ist, bleiben alle Änderungen des Werts auch nach Ausführung der Funktion erhalten.

Sie können ein Tupel als Rückgabewert verwenden, um beliebige out-Parameter in .NET-Bibliotheksmethoden zu speichern. Alternativ können Sie den out-Parameter auch als byref-Parameter behandeln. Beides wird im folgenden Codebeispiel veranschaulicht.

// TryParse has a second parameter that is an out parameter
// of type System.DateTime.
let (b, dt) = System.DateTime.TryParse("12-20-04 12:21:00")

printfn "%b %A" b dt

// The same call, using an address of operator.
let mutable dt2 = System.DateTime.Now
let b2 = System.DateTime.TryParse("12-20-04 12:21:00", &dt2)

printfn "%b %A" b2 dt2

Parameterarrays

Gelegentlich ist es erforderlich, eine Funktion zu definieren, die eine beliebige Anzahl von Parametern mit heterogenem Typ akzeptiert. Es wäre nicht sinnvoll, alle möglichen überladenen Methoden zu erstellen, um alle Typen zu berücksichtigen, die verwendet werden könnten. Die .NET-Implementierungen über das Feature „Parameterarray“ bieten Unterstützung für solche Methoden. Eine Methode mit einem Parameterarray in ihrer Signatur kann mit einer beliebigen Anzahl von Parametern bereitgestellt werden. Die Parameter werden in ein Array eingefügt. Der Typ der Arrayelemente bestimmt die Parametertypen, die an die Funktion übergeben werden können. Wenn Sie das Parameterarray mit System.Object als Elementtyp definieren, kann Clientcode Werte eines beliebigen Typs übergeben.

In F# können Parameterarrays nur in Methoden definiert werden. Sie können nicht in eigenständigen oder in Modulen definierten Funktionen verwendet werden.

Sie definieren ein Parameterarray mithilfe des Attributs ParamArray. Das ParamArray-Attribut kann nur auf den letzten Parameter angewendet werden.

Der folgende Code veranschaulicht sowohl das Aufrufen einer .NET-Methode, die ein Parameterarray akzeptiert, als auch die Definition eines Typs in F# mit einer Methode, die ein Parameterarray akzeptiert.

open System

type X() =
    member this.F([<ParamArray>] args: Object[]) =
        for arg in args do
            printfn "%A" arg

[<EntryPoint>]
let main _ =
    // call a .NET method that takes a parameter array, passing values of various types
    Console.WriteLine("a {0} {1} {2} {3} {4}", 1, 10.0, "Hello world", 1u, true)

    let xobj = new X()
    // call an F# method that takes a parameter array, passing values of various types
    xobj.F("a", 1, 10.0, "Hello world", 1u, true)
    0

Bei Ausführung in einem Projekt sieht die Ausgabe des vorherigen Codes wie folgt aus:

a 1 10 Hello world 1 True
"a"
1
10.0
"Hello world"
1u
true

Siehe auch