Klassen (F#)

Klassen sind Typen, die Objekte darstellen. Diese können über Eigenschaften, Methoden und Ereignisse verfügen.

Syntax

// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...

Bemerkungen

Klassen stellen die grundlegende Beschreibung von .NET-Objekttypen dar. Die Klasse ist das primäre Typkonzept, das objektorientierte Programmierung in F# unterstützt.

In der obigen Syntax ist type-name ein beliebiger gültiger Bezeichner. type-params beschreibt optionale generische Typparameter. Das Element besteht aus Typparameternamen und Einschränkungen, die in spitze Klammern (< und >) eingeschlossen sind. Weitere Informationen finden Sie unter Generics und Einschränkungen. parameter-list beschreibt Konstruktorparameter. Der erste Zugriffsmodifizierer bezieht sich auf den Typ, der zweite auf den primären Konstruktor. In beiden Fällen ist public der Standardwert.

Sie geben die Basisklasse für eine Klasse mithilfe des Schlüsselworts inherit an. Sie müssen Argumente in Klammern für den Basisklassenkonstruktor angeben.

Mithilfe von let-Bindungen deklarieren Sie Felder oder Funktionswerte, die für die Klasse lokal sind, und Sie müssen die allgemeinen Regeln für let-Bindungen befolgen. Der Abschnitt do-bindings enthält Code, der bei der Objektkonstruktion ausgeführt werden soll.

member-list besteht aus zusätzlichen Konstruktoren, Deklarationen für Instanzen und statische Methoden, Schnittstellendeklarationen, abstrakten Bindungen sowie Eigenschaften- und Ereignisdeklarationen. Diese werden unter Member beschrieben.

Das identifier-Element, das mit dem optionalen as-Schlüsselwort verwendet wird, gibt der Instanzvariablen oder dem Selbstbezeichner einen Namen, der in der Typdefinition verwendet werden kann, um auf die Instanz des Typs zu verweisen. Weitere Informationen finden Sie im Abschnitt „Selbstbezeichner“ weiter unten in diesem Thema.

Die Schlüsselwörter class und end, die den Anfang und das Ende der Definition markieren, sind optional.

Wechselseitig rekursive Typen (Typen, die auf einander verweisen) werden genau wie wechselseitig rekursive Funktionen mit dem Schlüsselwort and verknüpft. Ein Beispiel finden Sie im Abschnitt „Wechselseitig rekursive Typen“.

Konstruktoren

Der Konstruktor ist Code, der eine Instanz des Klassentyps erstellt. Konstruktoren für Klassen funktionieren in F# etwas anders als in anderen .NET-Sprachen. In einer F#-Klasse gibt es immer einen primären Konstruktor, dessen Argumente in der auf den Typnamen folgenden Parameterliste (parameter-list) beschrieben werden und dessen Textkörper aus den Bindungen vom Typ let (und let rec) am Anfang der Klassendeklaration und den darauffolgenden Bindungen vom Typ do besteht. Die Argumente des primären Konstruktors befinden sich in der gesamten Klassendeklaration im Gültigkeitsbereich.

Sie können zusätzliche Konstruktoren hinzufügen, indem Sie wie folgt das new-Schlüsselwort zum Hinzufügen eines Elements verwenden:

new(argument-list) = constructor-body

Der Textkörper des neuen Konstruktors muss den primären Konstruktor aufrufen, der oben in der Klassendeklaration angegeben ist.

Dieses Konzept wird anhand des folgenden Beispiels veranschaulicht. Im folgenden Code verfügt MyClass über zwei Konstruktoren: einen primären Konstruktor, der zwei Argumente akzeptiert, und einen weiteren Konstruktor, der keine Argumente akzeptiert.

type MyClass1(x: int, y: int) =
    do printfn "%d %d" x y
    new() = MyClass1(0, 0)

Bindungen vom Typ „let“ und „do“

Die Bindungen let und do in einer Klassendefinition bilden den Textkörper des primären Klassenkonstruktors und werden daher immer dann ausgeführt, wenn eine Klasseninstanz erstellt wird. Wenn eine let-Bindung eine Funktion ist, wird sie in einen Member kompiliert. Wenn es sich bei der let-Bindung um einen Wert handelt, der in keiner Funktion oder keinem Element verwendet wird, wird sie in eine Variable kompiliert, die für den Konstruktor lokal ist. Andernfalls wird sie in ein Feld der Klasse kompiliert. Die darauffolgenden do-Ausdrücke werden in den primären Konstruktor kompiliert und führen Initialisierungscode für jede Instanz aus. Da alle zusätzlichen Konstruktoren stets den primären Konstruktor aufrufen, werden die let-Bindungen und do-Bindungen immer unabhängig davon ausgeführt, welcher Konstruktor aufgerufen wird.

Auf durch let-Bindungen erstellte Felder kann über die Methoden und Eigenschaften der Klasse zugegriffen werden. Auf sie kann jedoch nicht über statische Methoden zugegriffen werden, auch wenn die statischen Methoden eine Instanzvariable als Parameter akzeptieren. Auf sie kann nicht mithilfe des Selbstbezeichners zugegriffen werden, sofern vorhanden.

Selbstbezeichner

Ein Selbstbezeichner ist ein Name, der die aktuelle Instanz darstellt. Selbstbezeichner ähneln dem Schlüsselwort this in C# oder C++ bzw. Me in Visual Basic. Sie können einen Selbstbezeichner auf zwei verschiedene Arten definieren, je nachdem, ob sich der Selbstbezeichner im Gültigkeitsbereich für die gesamte Klassendefinition oder nur für eine einzelne Methode befinden soll.

Um einen Selbstbezeichner für die gesamte Klasse zu definieren, verwenden Sie das Schlüsselwort as nach der schließenden Klammer der Konstruktorparameterliste, und geben Sie den Bezeichnernamen an.

Um einen Selbstbezeichner für nur eine Methode zu definieren, geben Sie den Selbstbezeichner in der Memberdeklaration direkt vor dem Methodennamen an. Verwenden Sie einen Punkt (.) als Trennzeichen.

Im folgenden Codebeispiel werden die beiden Möglichkeiten zum Erstellen eines Selbstbezeichners veranschaulicht. In der ersten Zeile wird das Schlüsselwort as verwendet, um den Selbstbezeichner zu definieren. In der fünften Zeile wird der Bezeichner this verwendet, um einen Selbstbezeichner zu definieren, dessen Gültigkeitsbereich auf die Methode PrintMessage beschränkt ist.

type MyClass2(dataIn) as self =
    let data = dataIn
    do
        self.PrintMessage()
    member this.PrintMessage() =
        printf "Creating MyClass2 with Data %d" data

Im Gegensatz zu anderen .NET-Sprachen können Sie den Selbstbezeichner beliebig benennen. Sie sind nicht auf Namen wie self, Me oder this beschränkt.

Der Selbstbezeichner, der mit dem Schlüsselwort as deklariert wird, wird erst nach dem Basiskonstruktor initialisiert. Daher wird bei Verwendung vor oder innerhalb des Basiskonstruktors während der Laufzeit System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. ausgelöst. Sie können den Selbstbezeichner nach dem Basiskonstruktor frei verwenden, etwa in let-Bindungen oder do-Bindungen.

Generische Typparameter

Generische Typparameter werden in spitzen Klammern (< und >) in Form eines einzelnen Anführungszeichens gefolgt von einem Bezeichner angegeben. Mehrere generische Typparameter werden durch Kommas getrennt. Der generische Typparameter befindet sich in der gesamten Deklaration im Gültigkeitsbereich. Im folgenden Codebeispiel wird gezeigt, wie Sie generische Typparameter angeben:

type MyGenericClass<'a>(x: 'a) =
    do printfn "%A" x

Typargumente werden bei Verwendung des Typs abgeleitet. Im folgenden Code ist der abgeleitete Typ eine Sequenz von Tupeln:

let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })

Angeben der Vererbung

Die inherit-Klausel identifiziert die direkte Basisklasse, sofern vorhanden. In F# ist nur eine direkte Basisklasse zulässig. Schnittstellen, die von einer Klasse implementiert werden, gelten nicht als Basisklassen. Weitere Informationen zu Schnittstellen finden Sie im Thema Schnittstellen (F#).

Sie können über die abgeleitete Klasse auf die Methoden und Eigenschaften der Basisklasse zugreifen. Verwenden Sie dazu das Sprachenschlüsselwort base als Bezeichner, gefolgt von einem Punkt (.) und dem Namen des Members.

Weitere Informationen finden Sie unter Vererbung.

Abschnitt „Member“

In diesem Abschnitt können Sie statische Methoden oder Instanzmethoden, Eigenschaften, Schnittstellenimplementierungen, abstrakte Member, Ereignisdeklarationen und zusätzliche Konstruktoren definieren. let- und do-Bindungen dürfen in diesem Abschnitt nicht verwendet werden. Da Member neben Klassen auch einer Vielzahl von F#-Typen hinzugefügt werden können, werden sie in einem separaten Thema (Member) erläutert.

Wechselseitig rekursive Typen

Wenn Sie Typen definieren, die zirkulär aufeinander verweisen, verknüpfen Sie die Typdefinitionen mithilfe des Schlüsselworts and. Das Schlüsselwort and ersetzt wie folgt das Schlüsselwort type in allen Definitionen außer der ersten:

open System.IO

type Folder(pathIn: string) =
    let path = pathIn
    let filenameArray: string array = Directory.GetFiles(path)
    member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray

and File(filename: string, containingFolder: Folder) =
    member this.Name = filename
    member this.ContainingFolder = containingFolder

let folder1 = new Folder(".")

for file in folder1.FileArray do
    printfn "%s" file.Name

Die Ausgabe ist eine Liste aller Dateien im aktuellen Verzeichnis.

Verwendung von Klassen, Unions, Datensätzen und Strukturen

Angesichts der Vielfalt der zur Auswahl stehenden Typen müssen Sie genau wissen, wofür die einzelnen Typen konzipiert sind, um den geeigneten Typ für eine bestimmte Situation auszuwählen. Klassen sind für die Verwendung in objektorientierten Programmierkontexten konzipiert. Objektorientierte Programmierung ist das vorherrschende Paradigma für Anwendungen, die für .NET Framework geschrieben werden. Klassen sind wahrscheinlich geeignet, wenn eine enge Interoperabilität zwischen Ihrem F#-Code und .NET Framework oder einer anderen objektorientierten Bibliothek erforderlich ist, und insbesondere dann, wenn eine Erweiterung von einem objektorientierten Typsystem wie einer UI-Bibliothek notwendig ist.

Wenn keine enge Interoperabilität mit objektorientiertem Code erforderlich ist oder wenn Sie Code schreiben, der eigenständig und daher vor häufiger Interaktion mit objektorientiertem Code geschützt ist, sollten Sie eine Kombination aus Klassen, Datensätzen und Unterscheidungs-Unions verwenden. Eine einzelne, gut durchdachte Unterscheidungs-Union kann zusammen mit geeignetem Musterabgleichscode häufig als einfachere Alternative zu einer Objekthierarchie verwendet werden. Informationen zu Unterscheidungs-Unions finden Sie unter Unterscheidungs-Unions.

Datensätze haben den Vorteil, dass sie einfacher als Klassen sind. Datensätze sind jedoch nicht geeignet, wenn die Anforderungen eines Typs über das hinausgehen, was mit ihrer Einfachheit erreicht werden kann. Datensätze sind im Grunde einfache Aggregate von Werten ohne separate Konstruktoren, die benutzerdefinierte Aktionen ausführen können, ohne ausgeblendete Felder und ohne Vererbung oder Schnittstellenimplementierungen. Member wie Eigenschaften und Methoden können zwar Datensätzen hinzugefügt werden, um ihr Verhalten komplexer zu gestalten, die in einem Datensatz gespeicherten Felder sind aber immer noch ein einfaches Aggregat von Werten. Weitere Informationen zu Datensätzen finden Sie unter Datensätze (F#).

Strukturen sind auch für kleine Datenaggregate nützlich, unterscheiden sich aber von Klassen und Datensätzen darin, dass es sich um .NET-Werttypen handelt. Klassen und Datensätze sind .NET-Verweistypen. Die Semantik von Werttypen und Verweistypen unterscheidet sich darin, dass Werttypen nach Wert übergeben werden. Das bedeutet, dass sie Bit für Bit kopiert werden, wenn sie als Parameter übergeben oder von einer Funktion zurückgegeben werden. Sie werden auch im Stapel gespeichert oder – bei Verwendung als Feld – im übergeordneten Objekt eingebettet und nicht an einem eigenen separaten Ort im Heap gespeichert. Daher eignen sich Strukturen für Daten, auf die häufig zugegriffen wird, wenn der Mehraufwand beim Zugriff auf den Heap ein Problem darstellt. Weitere Informationen zu Strukturen finden Sie unter Strukturen.

Weitere Informationen