Umwandlung und Konvertierungen (F#)
In diesem Artikel wird die Unterstützung für Typkonvertierungen in F# beschrieben.
Arithmetische Typen
F# stellt Konvertierungsoperatoren für arithmetische Konvertierungen zwischen verschiedenen primitiven Typen bereit, z. B. zwischen Ganzzahl- und Gleitkommatypen. Die Konvertierungsoperatoren für integrale Typen und Char-Typen enthalten aktivierte und nicht aktivierte Formulare, Gleitkommaoperatoren und der Konvertierungsoperator enum
dagegen nicht. Die nicht aktivierten Formulare werden in FSharp.Core.Operators
definiert, die aktivierten Formulare in FSharp.Core.Operators.Checked
. Die aktivierten Formulare überprüfen, ob ein Überlauf vorhanden ist, und generieren eine Runtimeausnahme, wenn der resultierende Wert die Grenzwerte des Zieltyps überschreitet.
Jeder dieser Operatoren hat denselben Namen wie der Zieltyp. Im folgenden Code, in dem die Typen explizit kommentiert sind, wird beispielsweise byte
mit zwei unterschiedlichen Bedeutungen angezeigt. Im ersten Fall wird der Typ und im zweiten Fall der Konvertierungsoperator angegeben.
let x : int = 5
let b : byte = byte x
In der folgenden Tabelle sind Konvertierungsoperatoren aufgeführt, die in F# definiert sind.
Operator | BESCHREIBUNG |
---|---|
byte |
Konvertiert in Byte, ein 8-Bit-Typ ohne Vorzeichen. |
sbyte |
Konvertiert in Byte mit Vorzeichen. |
int16 |
Konvertiert in eine 16-Bit-Ganzzahl mit Vorzeichen. |
uint16 |
Konvertiert in eine 16-Bit-Ganzzahl ohne Vorzeichen. |
int32, int |
Konvertiert in eine 32-Bit-Ganzzahl mit Vorzeichen. |
uint32 |
Konvertiert in eine 32-Bit-Ganzzahl ohne Vorzeichen. |
int64 |
Konvertiert in eine 64-Bit-Ganzzahl mit Vorzeichen. |
uint64 |
Konvertiert in eine 64-Bit-Ganzzahl ohne Vorzeichen. |
nativeint |
Konvertiert in eine native Ganzzahl. |
unativeint |
Konvertieren in eine native Ganzzahl ohne Vorzeichen. |
float, double |
Konvertiert in eine 64-Bit-IEEE-Gleitkommazahl mit doppelter Genauigkeit. |
float32, single |
Konvertiert in eine 32-Bit-IEEE-Gleitkommazahl mit einfacher Genauigkeit. |
decimal |
Konvertiert in System.Decimal . |
char |
Konvertiert in System.Char , ein Unicodezeichen. |
enum |
Konvertiert in einen Enumerationstyp. |
Neben den integrierten primitiven Typen können Sie diese Operatoren auch mit Typen verwenden, die op_Explicit
- oder op_Implicit
-Methoden mit bzw. ohne Vorzeichen implementieren. Der Konvertierungsoperator int
funktioniert beispielsweise mit jedem Typ, der eine statische op_Explicit
-Methode verwendet, die den Typ als Parameter verwendet und int
zurückgibt. Als besondere Ausnahme von der allgemeinen Regel, dass Methoden nicht vom Rückgabetyp überladen werden können, ist dies bei op_Explicit
und op_Implicit
möglich.
Enumerationstypen
Der Operator enum
ist ein generischer Operator, der einen Typparameter verwendet, der den Typ von enum
darstellt, in den konvertiert werden soll. Beim Konvertieren in einen Enumerationstyp wird durch Typrückschluss versucht, den Typ von enum
zu ermitteln, in den Sie konvertieren möchten. Im folgenden Beispiel wird die Variable col1
nicht explizit kommentiert, der Typ wird jedoch vom späteren Gleichheitstest abgeleitet. Daher kann der Compiler ableiten, dass Sie in eine Color
-Enumeration konvertieren. Alternativ können Sie wie im folgenden Beispiel eine Typanmerkung wie bei col2
bereitstellen.
type Color =
| Red = 1
| Green = 2
| Blue = 3
// The target type of the conversion cannot be determined by type inference, so the type parameter must be explicit.
let col1 = enum<Color> 1
// The target type is supplied by a type annotation.
let col2 : Color = enum 2
Sie können den Zielenumerationstyp auch wie im folgenden Code explizit als Typparameter angeben:
let col3 = enum<Color> 3
Die Enumerationsumwandlung funktioniert jedoch nur, wenn der zugrunde liegende Enumerationstyp mit dem zu konvertierenden Typ kompatibel ist. Im folgenden Code kann die Konvertierung aufgrund des Konflikts zwischen int32
und uint32
nicht kompiliert werden.
// Error: types are incompatible
let col4 : Color = enum 2u
Weitere Informationen finden Sie unter Enumerationen.
Umwandeln von Objekttypen
Die Konvertierung zwischen Typen in einer Objekthierarchie ist bei der objektorientierten Programmierung von entscheidender Bedeutung. Es gibt zwei grundlegende Arten der Konvertierung: Umwandlung in eine Basisklasse und Umwandlung in eine abgeleitete Klasse. Bei der Umwandlung einer Hierarchie in eine Basisklasse erfolgt die Umwandlung von einem abgeleiteten Objektverweis in einen Basisobjektverweis. Eine solche Umwandlung ist garantiert funktionsfähig, solange sich die Basisklasse in der Vererbungshierarchie der abgeleiteten Klasse befindet. Die Umwandlung einer Hierarchie von einem Basisobjektverweis in einen abgeleiteten Objektverweis ist nur dann erfolgreich, wenn es sich bei dem Objekt tatsächlich um eine Instanz des entsprechenden Zieltyps (abgeleitet) oder um einen vom Zieltyp abgeleiteten Typ handelt.
F# stellt Operatoren für diese Arten von Konvertierungen bereit. Der Operator :>
wandelt die Hierarchie in eine Basisklasse um, während der Operator :?>
die Hierarchie in eine abgeleitete Klasse umwandelt.
Umwandlung in eine Basisklasse
In vielen objektorientierten Sprachen erfolgt die Umwandlung in eine Basisklasse implizit. In F# gelten etwas andere Regeln. Eine Umwandlung in eine Basisklasse wird automatisch durchgeführt, wenn Argumente an Methoden in einem Objekttyp übergeben werden. Bei let-bound-Funktionen in einem Modul wird die Umwandlung in eine Basisklasse nur dann automatisch durchgeführt, wenn der Parametertyp als flexibler Typ deklariert ist. Weitere Informationen finden Sie unter Flexible Typen.
Der Operator :>
führt eine statische Umwandlung durch, was bedeutet, dass der Erfolg der Umwandlung zur Kompilierzeit bestimmt wird. Wenn eine Umwandlung, die :>
verwendet, erfolgreich kompiliert wird, handelt es sich um eine gültige Umwandlung, für die zur Laufzeit keine Gefahr eines Fehlers besteht.
Sie können auch den Operator upcast
verwenden, um eine solche Konvertierung auszuführen. Der folgende Ausdruck gibt eine Umwandlung der Hierarchie in eine Basisklasse an:
upcast expression
Wenn Sie den Operator upcast verwenden, leitet der Compiler den Typ, in den Sie konvertieren, aus dem Kontext ab. Wenn der Compiler den Zieltyp nicht bestimmen kann, meldet der Compiler einen Fehler. Dann ist möglicherweise eine Typanmerkung erforderlich.
Umwandlung in eine abgeleitete Klasse
Der Operator :?>
führt eine dynamische Umwandlung durch, was bedeutet, dass der Erfolg der Umwandlung zur Laufzeit bestimmt wird. Eine Umwandlung, die den Operator :?>
verwendet, wird nicht zur Kompilierzeit überprüft. Stattdessen wird zur Laufzeit eine Umwandlung in den angegebenen Typ durchgeführt. Wenn das Objekt mit dem Zieltyp kompatibel ist, wird die Umwandlung erfolgreich durchgeführt. Wenn das Objekt nicht mit dem Zieltyp kompatibel ist, wird zur Laufzeit eine InvalidCastException
ausgelöst.
Sie können auch den Operator downcast
verwenden, um eine dynamische Typkonvertierung durchzuführen. Der folgende Ausdruck gibt eine Konvertierung der Hierarchie in einen Typ an, der aus dem Programmkontext abgeleitet wird:
downcast expression
Wie beim upcast
-Operator meldet der Compiler einen Fehler, wenn er aus dem Kontext keinen bestimmten Zieltyp ableiten kann. Dann ist möglicherweise eine Typanmerkung erforderlich.
Im folgenden Code wird die Verwendung der Operatoren :>
und :?>
veranschaulicht. Anhand des Codes wird deutlich, dass Sie den Operator :?>
am besten dann verwenden, wenn Sie wissen, dass die Konvertierung erfolgreich verläuft, da eine InvalidCastException
ausgelöst wird, wenn bei der Konvertierung ein Fehler auftritt. Wenn Sie nicht wissen, ob die Konvertierung erfolgreich verläuft, sollten Sie besser einen Typtest verwenden, der einen match
-Ausdruck verwendet, sodass keine Ausnahme generiert werden muss.
type Base1() =
abstract member F : unit -> unit
default u.F() =
printfn "F Base1"
type Derived1() =
inherit Base1()
override u.F() =
printfn "F Derived1"
let d1 : Derived1 = Derived1()
// Upcast to Base1.
let base1 = d1 :> Base1
// This might throw an exception, unless
// you are sure that base1 is really a Derived1 object, as
// is the case here.
let derived1 = base1 :?> Derived1
// If you cannot be sure that b1 is a Derived1 object,
// use a type test, as follows:
let downcastBase1 (b1 : Base1) =
match b1 with
| :? Derived1 as derived1 -> derived1.F()
| _ -> ()
downcastBase1 base1
Da die generischen Operatoren downcast
und upcast
Argument- und Rückgabetyp per Typrückschluss ermitteln, können Sie let base1 = d1 :> Base1
in im obigen Codebeispiel durch let base1: Base1 = upcast d1
ersetzen.
Da upcast
selbst die Basisklasse nicht ermitteln konnte, ist eine Typanmerkung erforderlich.
Implizite Umwandlung in eine Basisklasse
Implizite Umwandlungen in eine Basisklasse werden in den folgenden Situationen eingesetzt:
Bei der Bereitstellung eines Parameters für eine Funktion oder Methode mit einem bekannten benannten Typ. Das schließt die Konvertierung von Konstrukten wie etwa von Berechnungsausdrücken oder die Aufteilung in Slices in einen Methodenaufruf mit ein.
Beim Zuweisen oder Ändern eines Datensatzfelds oder einer Datensatzeigenschaft, das bzw. die einen bekannten benannten Typ aufweist.
Wenn ein Branch eines
if/then/else
- odermatch
-Ausdrucks einen bekannten Zieltyp aufweist, der sich aus einem anderen Branch oder allgemein bekannten Typ ableitet.Wenn ein Element einer Liste, eines Arrays oder eines Sequenzausdrucks einen bekannten Zieltyp aufweist.
Beachten Sie z. B. folgenden Code:
open System
open System.IO
let findInputSource () : TextReader =
if DateTime.Now.DayOfWeek = DayOfWeek.Monday then
// On Monday a TextReader
Console.In
else
// On other days a StreamReader
File.OpenText("path.txt")
Hier berechnen die Branches der Bedingung einen TextReader
bzw. einen StreamReader
. Im zweiten Branch wird der bekannte Zieltyp TextReader
aus der Typanmerkung in der Methode und aus dem ersten Branch verwendet. Das bedeutet, dass im zweiten Branch keine Umwandlung in eine Basisklasse erforderlich ist.
Wenn an jedem Punkt, an dem eine zusätzliche implizite Umwandlung in eine Basisklasse durchgeführt wird, eine Warnung angezeigt werden soll, können Sie die Warnung 3388 (/warnon:3388
oder <WarnOn>3388</WarnOn>
-Eigenschaft) aktivieren.
Implizite numerische Konvertierungen
In F# wird meist eine explizite Erweiterung numerischer Typen mithilfe von Konvertierungsoperatoren verwendet. Die explizite Erweiterung wird beispielsweise für die meisten numerischen Typen wie int8
oder int16
oder zur Erweiterung von float32
auf float64
oder bei unbekanntem Quell- oder Zieltyp benötigt.
Die implizite Erweiterung ist jedoch für 32-Bit-Ganzzahlen, die auf 64-Bit-Ganzzahlen erweitert werden, in denselben Situationen wie eine implizite Umwandlung in eine Basisklasse zulässig. Sehen Sie sich beispielsweise eine typische API-Form an:
type Tensor(…) =
static member Create(sizes: seq<int64>) = Tensor(…)
Ganzzahlenliterale für int64 können verwendet werden:
Tensor.Create([100L; 10L; 10L])
Oder Ganzzahlenliterale für int32:
Tensor.Create([int64 100; int64 10; int64 10])
Die Erweiterung erfolgt automatisch von int32
auf int64
, von int32
auf nativeint
und von int32
auf double
, wenn während des Typrückschlusses sowohl Quell- als auch Zieltyp bekannt sind. In Fällen wie den obigen Beispielen können also int32
-Literale verwendet werden:
Tensor.Create([100; 10; 10])
Sie können optional auch die Warnung 3389 (/warnon:3389
oder <WarnOn>3389</WarnOn>
-Eigenschaft) aktivieren, sodass an jedem Punkt, an dem eine implizite numerische Erweiterung verwendet wird, eine Warnung angezeigt wird.
Implizite Konvertierungen im .NET-Stil
.NET-APIs ermöglichen die Definition von statischen op_Implicit
-Methoden, um implizite Konvertierungen zwischen Typen bereitzustellen. Diese werden im F#-Code automatisch angewendet, wenn Argumente an Methoden übergeben werden. Betrachten Sie beispielsweise den folgenden Code, der op_Implicit
-Methoden explizit aufruft:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")
op_Implicit
-Konvertierungen im .NET-Stil werden automatisch auf Argumentausdrücke angewendet, wenn die Typen für Quellausdruck und Zieltyp verfügbar sind:
open System.Xml.Linq
let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")
Sie können optional auch die Warnung 3395 (/warnon:3395
oder <WarnOn>3395</WarnOn>
-Eigenschaft) aktivieren, sodass an jedem Punkt, an dem eine implizite Konvertierung im .NET-Stil verwendet wird, eine Warnung angezeigt wird.
op_Implicit
-Konvertierungen im .NET-Stil werden auch auf Nicht-Methoden-Argumentausdrücke in denselben Situationen wie implizite Umwandlungen in eine Basisklasse automatisch angewendet. Bei häufiger oder unangemessener Verwendung können implizite Konvertierungen jedoch nur schlecht mit dem Typrückschluss interagieren und ergeben einen Code, der schwieriger zu verstehen ist. Daher generieren sie immer Warnungen, wenn sie in Positionen ohne Argumente verwendet werden.
Wenn an jedem Punkt, an dem für ein Nicht-Methoden-Argument eine implizite Konvertierung im .NET-Stil verwendet wird, eine Warnung angezeigt werden soll, können Sie die Warnung 3391 (/warnon:3391
oder <WarnOn>3391</WarnOn>
-Eigenschaft) aktivieren.
Zusammenfassung der Warnungen im Zusammenhang mit Konvertierungen
Die folgenden optionalen Warnungen werden für die Verwendung impliziter Konvertierungen bereitgestellt:
/warnon:3388
(zusätzlicher impliziter Upcast)/warnon:3389
(implizite numerische Erweiterung)/warnon:3391
(op_Implicit
bei Nicht-Methoden-Argumenten, standardmäßig aktiviert)/warnon:3395
(op_Implicit
bei Methodenargumenten)