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- oder match-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.

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)

Weitere Informationen