Konstruktoren

In diesem Artikel wird beschrieben, wie Sie Konstruktoren definieren und mit ihnen Klassen- und Strukturobjekte erstellen und initialisieren.

Konstruktion von Klassenobjekten

Objekte der Klassentypen haben Konstruktoren. Es gibt zwei Arten von Konstruktoren. Zum einen den primären Konstruktor, dessen Parameter direkt nach dem Typnamen in Klammern angezeigt werden. Andere, optionale zusätzliche Konstruktoren legen Sie mit dem Schlüsselwort new fest. Solche zusätzlichen Konstruktoren müssen den primären Konstruktor aufrufen.

Der primäre Konstruktor enthält let- und do-Bindungen, die am Anfang der Klassendefinition angezeigt werden. Eine let-Bindung deklariert private Felder und Methoden der Klasse, eine do-Bindung führt Code aus. Weitere Informationen zu let-Bindungen in Klassenkonstruktoren finden Sie unter let-Bindungen in Klassen. Weitere Informationen zu do-Bindungen in Konstruktoren finden Sie unter do-Bindungen in Klassen.

Unabhängig davon, ob der Konstruktor, den Sie aufrufen möchten, ein primärer oder ein zusätzlicher Konstruktor ist, können Sie Objekte mit einem new-Ausdruck erstellen, mit dem optionalen Schlüsselwort new oder ohne. Sie initialisieren Ihre Objekte zusammen mit Konstruktorargumenten, indem Sie entweder die Argumente der Reihenfolge nach, durch Kommas voneinander getrennt und in Klammern eingeschlossen auflisten oder benannte Argumente und Werte in Klammern verwenden. Bei der Konstruktion eines Objekts können Sie auch die Eigenschaft des Objekts festlegen: Verwenden Sie dazu die Eigenschaftsnamen, und weisen Sie ihnen Werte zu, so, wie Sie auch benannte Konstruktorargumente verwenden.

Der folgende Code zeigt eine Klasse, die einen Konstruktor aufweist, und mehrere Möglichkeiten der Objekterstellung:

// This class has a primary constructor that takes three arguments
// and an additional constructor that calls the primary constructor.
type MyClass(x0, y0, z0) =
    let mutable x = x0
    let mutable y = y0
    let mutable z = z0
    do
        printfn "Initialized object that has coordinates (%d, %d, %d)" x y z
    member this.X with get() = x and set(value) = x <- value
    member this.Y with get() = y and set(value) = y <- value
    member this.Z with get() = z and set(value) = z <- value
    new() = MyClass(0, 0, 0)

// Create by using the new keyword.
let myObject1 = new MyClass(1, 2, 3)
// Create without using the new keyword.
let myObject2 = MyClass(4, 5, 6)
// Create by using named arguments.
let myObject3 = MyClass(x0 = 7, y0 = 8, z0 = 9)
// Create by using the additional constructor.
let myObject4 = MyClass()

Die Ausgabe lautet wie folgt:

Initialized object that has coordinates (1, 2, 3)
Initialized object that has coordinates (4, 5, 6)
Initialized object that has coordinates (7, 8, 9)
Initialized object that has coordinates (0, 0, 0)

Konstruktion von Strukturen

Strukturen folgen allen Regeln von Klassen. Sie können daher einen primären Konstruktor haben und zusätzliche Konstruktoren mit new angeben. Es gibt jedoch einen wichtigen Unterschied zwischen Strukturen und Klassen: Strukturen können einen parameterlosen Konstruktor aufweisen (also einen Konstruktor ohne Argumente), selbst wenn kein primärer Konstruktor definiert ist. Der parameterlose Konstruktor initialisiert alle Felder auf den Standardwert für diesen Typ; in der Regel Null oder ein entsprechender Wert. Konstruktoren, die Sie für Strukturen definieren, müssen mindestens ein Argument aufweisen, damit keine Konflikte mit dem parameterlosen Konstruktor entstehen.

Strukturen enthalten auch häufig Felder, die mit dem Schlüsselwort val erstellt wurden. Klassen können diese Felder auch enthalten. Strukturen und Klassen, die mit dem Schlüsselwort val definierte Felder enthalten, können auch in zusätzlichen Konstruktoren initialisiert werden. Dazu werden Datensatzausdrücke verwendet, wie im folgenden Code gezeigt.

type MyStruct =
    struct
       val X : int
       val Y : int
       val Z : int
       new(x, y, z) = { X = x; Y = y; Z = z }
    end

let myStructure1 = new MyStruct(1, 2, 3)

Weitere Informationen finden Sie unter Explizite Felder: Das Schlüsselwort val.

Ausführen von Nebeneffekten in Konstruktoren

Ein primärer Konstruktor in einer Klasse kann Code in einer do-Bindung ausführen. Aber wie können Sie Code in einem zusätzlichen Konstruktor ohne do-Bindung ausführen? Dafür verwenden Sie das Schlüsselwort then.

 // Executing side effects in the primary constructor and
// additional constructors.
type Person(nameIn : string, idIn : int) =
    let mutable name = nameIn
    let mutable id = idIn
    do printfn "Created a person object."
    member this.Name with get() = name and set(v) = name <- v
    member this.ID with get() = id and set(v) = id <- v
    new() =
        Person("Invalid Name", -1)
        then
            printfn "Created an invalid person object."

let person1 = new Person("Humberto Acevedo", 123458734)
let person2 = new Person()

Die Nebeneffekte des primären Konstruktors werden weiterhin ausgeführt. Daher sieht die Ausgabe wie folgt aus:

Created a person object.
Created a person object.
Created an invalid person object.

then ist anstatt eines weiteren do erforderlich, weil das Schlüsselwort do seine Standardbedeutung hat, nämlich einen Ausdruck, der unit zurückgibt, abzutrennen, wenn er im Körper eines zusätzlichen Konstruktors vorhanden ist. Es hat nur im Zusammenhang mit primären Konstruktoren eine besondere Bedeutung.

Selbstbezeichner in Konstruktoren

In anderen Membern geben Sie einen Namen für das aktuelle Objekt in der Definition jedes Members an. Sie können den Selbstbezeichner auch in die erste Zeile der Klassendefinition einfügen. Verwenden Sie dazu das Schlüsselwort as, direkt gefolgt von den Konstruktorparametern. Das folgende Beispiel zeigt diese Syntax.

type MyClass1(x) as this =
    // This use of the self identifier produces a warning - avoid.
    let x1 = this.X
    // This use of the self identifier is acceptable.
    do printfn "Initializing object with X =%d" this.X
    member this.X = x

Bei zusätzlichen Konstruktoren können Sie ebenfalls Selbstbezeichner definieren, indem Sie die as-Klausel direkt hinter den Konstruktorparametern einfügen. Das folgende Beispiel zeigt diese Syntax:

type MyClass2(x : int) =
    member this.X = x
    new() as this = MyClass2(0) then printfn "Initializing with X = %d" this.X

Wenn Sie versuchen, ein Objekt zu verwenden, bevor es vollständig definiert wurde, können Probleme auftreten. Aus diesem Grund kann es bei der Verwendung des Selbstbezeichners dazu kommen, dass der Compiler eine Warnung ausgibt und zusätzliche Überprüfungen hinzufügt, um sicherzustellen, dass erst nach der Initialisierung des Objekts auf die Member dieses Objekts zugegriffen wird. Den Selbstbezeichner sollten Sie nur in do-Bindungen des primären Konstruktors oder nach dem Schlüsselwort then in zusätzlichen Konstruktoren verwenden.

Der Name des Selbstbezeichners muss nicht this sein. Er kann ein beliebiger gültiger Bezeichner sein.

Zuweisen von Werten zu Eigenschaften bei der Initialisierung

Sie können den Eigenschaften eines Klassenobjekts im Initialisierungscode Werte zuweisen, indem Sie eine Liste von Zuweisungen des Formulars property = value an die Argumentliste für einen Konstruktor anfügen. Dies wird im folgenden Codebeispiel veranschaulicht:

 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)

Die folgende Version des vorherigen Codes zeigt die Kombination von gewöhnlichen Argumenten, optionalen Argumenten und Eigenschafteneinstellungen einem Konstruktoraufruf:

type Account(accountNumber : int, ?first: string, ?last: string, ?bal : float) =
   let mutable balance = defaultArg bal 0.0
   let mutable number = accountNumber
   let mutable firstName = defaultArg first ""
   let mutable lastName = defaultArg last ""
   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(8782108, bal = 543.33,
                          FirstName="Raman", LastName="Iyer")

Konstruktoren in geerbten Klassen

Beim Erben von einer Basisklasse, die einen Konstruktor enthält, müssen Sie die Argumente in der Inherit-Klausel angeben. Weitere Informationen finden Sie unter Konstruktoren und Vererbung.

Statische Konstruktoren oder Typkonstruktoren

Zusätzlich zum Angeben von Code für die Erstellung von Objekten können Sie auch statische let- und do-Bindungen in Klassentypen erstellen, die ausgeführt werden, bevor der Typ zum ersten Mal für die Initialisierung auf Typebene verwendet wird. Weitere Informationen finden Sie unter let-Bindungen in Klassen und do-Bindungen in Klassen.

Siehe auch