Klassen (F#)
Klassen sind Typen, die Objekte darstellen, die über Eigenschaften, Methoden und Ereignisse verfügen können.
// 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 ...
...
Hinweise
Klassen stellen die grundlegende Beschreibung von .NET-Objekttypen dar. Die Klasse ist das Konzept des primären Typs, das objektorientierte Programmierung in F# unterstützt.
In der vorangehenden Syntax ist der type-name jeder gültige Bezeichner.Das type-params beschreibt optionale generische Typparameter.Es besteht aus Typparameternamen und Einschränkungen, die in spitze Klammern (< und >) eingeschlossen sind.Weitere Informationen finden Sie unter Generika (F#) und Einschränkungen (F#).Die parameter-list beschreibt Konstruktorparameter.Der erste Zugriffsmodifizierer bezieht sich auf den Typ; der zweite bezieht sich auf den primären Konstruktor.In beiden Fällen ist der Standardwert public.
Sie geben die Basisklasse für eine Klasse mit dem inherit-Schlüsselwort an.Für den Basisklassenkonstruktor müssen Sie in Klammern eingeschlossene Argumente angeben.
Sie deklarieren Felder oder Funktionswerte, die in der Klasse lokal sind, mit let-Bindungen, und Sie müssen die allgemeinen Regeln für let-Bindungen befolgen.Der do-bindings-Abschnitt enthält Code, der bei der Objektkonstruktion ausgeführt werden soll.
Die member-list besteht aus zusätzlichen Konstruktoren, Deklarationen von Instanzmethoden und statischen Methoden, Schnittstellendeklarationen, abstrakten Bindungen sowie Eigenschaften- und Ereignisdeklarationen.Diese werden in Member (F#) beschrieben.
Mit dem identifier, der mit dem optionalen as-Schlüsselwort verwendet wird, erhält die Instanzvariable bzw. der 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 bzw. das Ende der Definition markieren, sind optional.
Wechselseitig rekursive Typen, d. h. Typen, die aufeinander verweisen, werden wie wechselseitig rekursive Funktionen mit dem and-Schlüsselwort zusammengefügt.Ein Beispiel finden Sie im Abschnitt Wechselseitig Rekursive Typen.
Konstruktoren
Beim Konstruktor handelt es sich um Code, der eine Instanz des Klassentyps erstellt.Die Funktionsweise der Konstruktoren für Klassen in F# unterscheidet sich etwas von der Funktionsweise in anderen .NET-Sprachen.In einer F#-Klasse ist immer ein primärer Konstruktor vorhanden, dessen Argumente in der parameter-list nach dem Typnamen beschrieben werden und dessen Text aus den let-Bindungen (und let rec-Bindungen) am Anfang der Klassendeklaration sowie den nachfolgenden do-Bindungen besteht.Der Gültigkeitsbereich der Argumente des primären Konstruktors umfasst die gesamte Klassendeklaration.
Sie können auf folgende Weise zusätzliche Konstruktoren hinzufügen, indem Sie mit dem new-Schlüsselwort einen Member hinzufügen:
new(argument-list) = constructor-body
Der Text des neuen Konstruktors muss den primären Konstruktor aufrufen, der am oberen Rand der Klassendeklaration angegeben wird.
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)
let-Bindungen und do-Bindungen
Die let-Bindung und die do-Bindung in einer Klassendefinition bilden den Text des primären Klassenkonstruktors, und daher werden sie immer ausgeführt, wenn eine Klasseninstanz erstellt wird.Ist eine let-Bindung eine Funktion, wird sie in einen Member kompiliert.Wenn die let-Bindung ein Wert ist, der in keiner Funktion oder keinem Member verwendet wird, wird sie zu einer Variable kompiliert, die für den Konstruktor lokal ist.Andernfalls wird sie in ein Feld der Klasse kompiliert.Die darauf folgenden do-Ausdrücke werden in den primären Konstruktor kompiliert und führen für jede Instanz Initialisierungscode aus.Da zusätzliche Konstruktoren immer den primären Konstruktor aufrufen, werden die let-Bindungen und die do-Bindungen immer ausgeführt, unabhängig von dem Konstruktor, der aufgerufen wird.
Auf Felder, die durch let-Bindungen erstellt werden, kann in allen Methoden und Eigenschaften der Klasse zugegriffen werden. Statische Methoden können jedoch nicht auf sie zugreifen, selbst wenn die statischen Methoden eine Instanzvariable als Parameter akzeptieren.Auf sie kann nicht mit dem ggf. vorhandenen Selbstbezeichner zugegriffen werden.
Selbstbezeichner
Ein Selbstbezeichner ist ein Name, der die aktuelle Instanz darstellt.Selbstbezeichner ähneln dem this-Schlüsselwort in C# oder C++ oder Me in Visual Basic.Sie können einen Selbstbezeichner auf zweierlei Weise definieren, je nachdem, ob der Gültigkeitsbereich des Selbstbezeichners die gesamte Klassendefinition oder nur eine einzelne Methode umfassen soll.
Verwenden Sie zum Definieren eines Selbstbezeichners für die gesamte Klasse das as-Schlüsselwort nach der schließenden Klammer der Konstruktorparameterliste, und geben Sie den Bezeichnernamen an.
Wenn Sie einen Selbstbezeichner für nur eine Methode definieren möchten, geben Sie den Selbstbezeichner in der Memberdeklaration an, direkt vor dem Methodennamen und mit einem Punkt (.) als Trennzeichen.
Im folgenden Codebeispiel werden die beiden Verfahren zum Erstellen eines Selbstbezeichners veranschaulicht.In der ersten Zeile wird der Selbstbezeichner mit dem as-Schlüsselwort definiert.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 Unterschied zu anderen .NET-Sprachen können Sie jedoch für den Selbstbezeichner einen beliebigen Namen festlegen, es muss nicht der Name self, Me oder this verwendet werden.
Der mit dem as-Schlüsselwort deklarierte Selbstbezeichner wird erst initialisiert, nachdem die let-Bindungen ausgeführt wurden.Daher kann er in den let-Bindungen nicht verwendet werden.Sie können den Selbstbezeichner im Abschnitt der do-Bindungen verwenden.
Parameter für generische Typen
Generische Typparameter werden in spitzen Klammern (< und >) angegeben, in Form eines Bezeichners nach einem einfachen Anführungszeichen.Mehrere generische Typparameter werden durch Kommas getrennt.Der Gültigkeitsbereich des generischen Typparameters umfasst die gesamte Deklaration.Im folgenden Codebeispiel wird gezeigt, wie generische Typparameter angegeben werden.
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
Typargumente werden abgeleitet, wenn der Typ verwendet wird.Im folgenden Code ist der abgeleitete Typ eine Sequenz von Tupeln.
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
Angeben von Vererbung
Die inherit-Klausel bezeichnet die direkte Basisklasse, sofern vorhanden.In F# ist nur eine direkte Basisklasse zulässig.Die von einer Klasse implementierten Schnittstellen werden nicht als Basisklassen betrachtet.Schnittstellen werden im Thema Schnittstellen (F#) erläutert.
Sie können auf die Methoden und Eigenschaften der Basisklasse von der abgeleiteten Klasse mit dem Sprachschlüsselwort base als Bezeichner gefolgt von einem Punkt (.) und dem Namen des Member zugreifen.
Weitere Informationen finden Sie unter Vererbung (F#).
Memberabschnitt
In diesem Abschnitt können Sie statische Methoden oder Instanzmethoden, Eigenschaften, Schnittstellenimplementierungen, abstrakte Member, Ereignisdeklarationen und zusätzliche Konstruktoren definieren.let- und do-Bindungen sind in diesem Abschnitt nicht zulässig.Da Member außer Klassen auch vielfältigen anderen F#-Typen hinzugefügt werden können, werden sie in dem gesonderten Thema Member (F#) erläutert.
Wechselseitig rekursive Typen
Wenn Sie Typen definieren, die zirkulär aufeinander verweisen, verbinden Sie die Typdefinitionen mithilfe des and-Schlüsselworts.Das and-Schlüsselwort ersetzt das type-Schlüsselwort in jeder Definition außer der ersten Definition wie folgt.
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.
Zweckmäßige Verwendung von Klassen, Unions, Datensätzen und Strukturen
In Anbetracht der Vielzahl von Typen, die zur Auswahl stehen, müssen Sie die vorgesehene Verwendung der einzelnen Typen kennen, um den geeigneten Typ für eine bestimmte Situation auszuwählen.Klassen sind zur Verwendung in Kontexten der objektorientierten Programmierung vorgesehen.Die objektorientierte Programmierung ist das vorherrschende Paradigma in Anwendungen, die für .NET Framework geschrieben wurden.Wenn F#-Code in enger Verbindung mit der .NET Framework-Bibliothek oder einer anderen objektorientierten Bibliothek ausgeführt werden muss, und insbesondere wenn Sie ein objektorientiertes Typsystem, z. B. eine UI-Bibliothek, erweitern müssen, sind Klassen wahrscheinlich angemessen.
Wenn keine enge Interoperation mit objektorientiertem Code erfolgt, oder wenn Sie Code schreiben, der in sich geschlossen und daher vor häufiger Interaktion mit objektorientiertem Code geschützt ist, empfiehlt sich möglicherweise die Verwendung von Datensätzen und Unterscheidungs-Unions.Als einfachere Alternative zu einer Objekthierarchie kann oft eine einzelne, zweckentsprechend entworfene Unterscheidungs-Union zusammen mit entsprechendem Mustervergleichscode verwendet werden.Weitere Informationen zu Unterscheidungs-Unions finden Sie unter Unterscheidungs-Union (F#).
Datensätze bieten den Vorteil, dass sie einfacher als Klassen sind, jedoch sind Datensätze ungeeignet, wenn sie nicht komplex genug sind, um die Anforderungen eines Typs zu erfüllen.Datensätze sind im Grunde einfache Aggregate von Werten, ohne eigene Konstruktoren, die benutzerdefinierte Aktionen ausführen können, ohne ausgeblendete Felder sowie ohne Vererbung oder Schnittstellenimplementierungen.Obwohl Datensätzen Member, z. B. Eigenschaften und Methoden, hinzugefügt werden können, um die Komplexität ihres Verhaltens zu erhöhen, sind die in einem Datensatz gespeicherten Felder immer noch ein einfaches Aggregat von Werten.Weitere Informationen zu Datensätzen finden Sie unter Datensätze (F#).
Strukturen sind als kleine Aggregate von Daten ebenfalls von Nutzen, sie unterscheiden sich jedoch von Klassen und Datensätzen dadurch, dass sie .NET-Werttypen sind.Klassen und Datensätze sind .NET-Verweistypen.Die Semantik von Werttypen und Verweistypen unterscheidet sich dadurch, dass Werttypen als Wert übergeben werden.Dies bedeutet, dass sie Bit für Bit kopiert werden, wenn sie als Parameter übergeben oder von einer Funktion zurückgegeben werden.Sie werden ebenfalls auf dem Stapel gespeichert oder, wenn sie als Feld verwendet werden, im übergeordneten Objekt eingebettet, statt an einer eigenen Position auf dem Heap gespeichert zu werden.Daher eignen sich Strukturen für Daten mit häufigem Zugriff, wenn der Mehraufwand für den Zugriff auf den Heap ein Problem darstellt.Weitere Informationen über Strukturen finden Sie unter Strukturen (F#).