Cast-conversies en conversies (F#)

In dit artikel wordt ondersteuning voor typeconversies in F# beschreven.

Rekenkundige typen

F# biedt conversieoperators voor rekenkundige conversies tussen verschillende primitieve typen, zoals tussen gehele getallen en drijvende kommatypen. De integrale en tekenconversieoperatoren hebben formulieren gecontroleerd en uitgeschakeld; de operatoren voor drijvende komma en de enum conversieoperator niet. De niet-gecontroleerde formulieren worden gedefinieerd en FSharp.Core.Operators de ingeschakelde formulieren worden gedefinieerd in FSharp.Core.Operators.Checked. De ingeschakelde formulieren controleren op overloop en genereren een runtime-uitzondering als de resulterende waarde de limieten van het doeltype overschrijdt.

Elk van deze operators heeft dezelfde naam als de naam van het doeltype. In de volgende code, waarin de typen expliciet worden geannoteerd, byte worden bijvoorbeeld weergegeven met twee verschillende betekenissen. Het eerste exemplaar is het type en de tweede is de conversieoperator.

let x : int = 5

let b : byte = byte x

In de volgende tabel ziet u conversieoperators die zijn gedefinieerd in F#.

Operator Beschrijving
byte Converteren naar byte, een 8-bits niet-ondertekend type.
sbyte Converteren naar ondertekende byte.
int16 Converteren naar een 16-bits geheel getal dat is ondertekend.
uint16 Converteren naar een 16-bits geheel getal zonder teken.
int32, int Converteren naar een 32-bits geheel getal dat is ondertekend.
uint32 Converteren naar een 32-bits geheel getal zonder teken.
int64 Converteren naar een 64-bits geheel getal dat is ondertekend.
uint64 Converteren naar een 64-bits geheel getal zonder teken.
nativeint Converteren naar een systeemeigen geheel getal.
unativeint Converteren naar een niet-ondertekend systeemeigen geheel getal.
float, double Converteer naar een 64-bits IEEE-drijvendekommanummer met dubbele precisie.
float32, single Converteer naar een 32-bits IEEE-drijvendekommagetal met één precisie.
decimal Converteren naar System.Decimal.
char Converteren naar System.Char, een Unicode-teken.
enum Converteren naar een geïnventariseerd type.

Naast ingebouwde primitieve typen kunt u deze operators gebruiken met typen die implementeren op_Explicit of op_Implicit methoden met de juiste handtekeningen. De int conversieoperator werkt bijvoorbeeld met elk type dat een statische methode op_Explicit biedt die het type als parameter gebruikt en retourneert int. Als een speciale uitzondering op de algemene regel dat methoden niet kunnen worden overbelast door retourtype, kunt u dit doen voor op_Explicit en op_Implicit.

Geïnventariseerd type

De enum operator is een algemene operator die één typeparameter gebruikt die het type vertegenwoordigt waarnaar enum moet worden geconverteerd. Wanneer deze wordt geconverteerd naar een geïnventariseerd type, probeert deductie te bepalen naar welk type enum u wilt converteren. In het volgende voorbeeld wordt de variabele col1 niet expliciet geannoteerd, maar het type wordt afgeleid van de latere gelijkheidstest. Daarom kan de compiler afleiden dat u converteert naar een Color opsomming. U kunt ook een typeaantekening opgeven, zoals col2 in het volgende voorbeeld.

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

U kunt ook het doelinventarisatietype expliciet opgeven als een typeparameter, zoals in de volgende code:

let col3 = enum<Color> 3

Houd er rekening mee dat de opsommingscasts alleen werken als het onderliggende type van de opsomming compatibel is met het type dat wordt geconverteerd. In de volgende code kan de conversie niet worden gecompileerd vanwege de verschillen tussen int32 en uint32.

// Error: types are incompatible
let col4 : Color = enum 2u

Zie Opsommingen voor meer informatie.

Objecttypen casten

Conversie tussen typen in een objecthiërarchie is fundamenteel voor objectgeoriënteerde programmering. Er zijn twee basistypen conversies: cast-up (upcasting) en cast-down (downcasting). Het gieten van een hiërarchie betekent casten van een afgeleide objectverwijzing naar een basisobjectverwijzing. Een dergelijke cast werkt gegarandeerd zolang de basisklasse zich in de overnamehiërarchie van de afgeleide klasse bevindt. Het omlaag gieten van een hiërarchie, van een basisobjectverwijzing naar een afgeleide objectverwijzing, slaagt alleen als het object daadwerkelijk een exemplaar is van het juiste doeltype (afgeleid) of een type dat is afgeleid van het doeltype.

F# biedt operators voor deze typen conversies. De :> operator gooit de hiërarchie omhoog en de :?> operator cast de hiërarchie omlaag.

Upcasting

In veel objectgeoriënteerde talen is upcasting impliciet; in F# zijn de regels iets anders. Upcasting wordt automatisch toegepast wanneer u argumenten doorgeeft aan methoden voor een objecttype. Voor let-gebonden functies in een module is upcasting echter niet automatisch, tenzij het parametertype wordt gedeclareerd als een flexibel type. Zie Flexibele typen voor meer informatie.

De :> operator voert een statische cast uit, wat betekent dat het succes van de cast tijdens het compileren wordt bepaald. Als een cast die compilaties gebruikt :> , is het een geldige cast en heeft het geen kans op fouten tijdens runtime.

U kunt de upcast operator ook gebruiken om een dergelijke conversie uit te voeren. Met de volgende expressie wordt een conversie van de hiërarchie opgegeven:

upcast expression

Wanneer u de upcast-operator gebruikt, probeert de compiler het type te afleiden waarnaar u converteert vanuit de context. Als de compiler het doeltype niet kan bepalen, meldt de compiler een fout. Mogelijk is een typeaantekening vereist.

Downcasting

De :?> operator voert een dynamische cast uit, wat betekent dat het succes van de cast tijdens runtime wordt bepaald. Een cast die gebruikmaakt van de operator wordt niet gecontroleerd tijdens het :?> compileren, maar tijdens runtime wordt geprobeerd om naar het opgegeven type te casten. Als het object compatibel is met het doeltype, slaagt de cast. Als het object niet compatibel is met het doeltype, genereert de runtime een InvalidCastException.

U kunt de downcast operator ook gebruiken om een dynamische typeconversie uit te voeren. Met de volgende expressie wordt een conversie naar beneden in de hiërarchie opgegeven naar een type dat wordt afgeleid van de programmacontext:

downcast expression

Wat de upcast operator betreft, als de compiler een specifiek doeltype niet kan afleiden uit de context, wordt er een fout gerapporteerd. Mogelijk is een typeaantekening vereist.

De volgende code illustreert het gebruik van de :> en :?> operators. De code illustreert dat de operator het beste wordt gebruikt wanneer u weet dat de :?> conversie slaagt, omdat deze wordt veroorzaakt InvalidCastException als de conversie mislukt. Als u niet weet dat een conversie slaagt, is een typetest die gebruikmaakt van een match expressie beter omdat hiermee de overhead van het genereren van een uitzondering wordt vermeden.

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

Omdat de algemene operators downcast en upcast afhankelijk zijn van typedeductie om het argument en het retourtype te bepalen, kunt u in het vorige codevoorbeeld vervangen door let base1 = d1 :> Base1let base1: Base1 = upcast d1.

Een typeaantekening is vereist, omdat upcast de basisklasse zelf niet kan worden bepaald.

Impliciete upcast-conversies

Impliciete upcasts worden ingevoegd in de volgende situaties:

  • Wanneer u een parameter opgeeft aan een functie of methode met een bekend benoemd type. Dit geldt ook wanneer een constructie zoals berekeningsexpressies of segmentering een methode-aanroep wordt.

  • Wanneer u een recordveld of eigenschap met een bekend benoemd type toewijst of dempt.

  • Wanneer een vertakking van een if/then/else of match expressie een bekend doeltype heeft dat voortvloeit uit een andere vertakking of algemeen bekend type.

  • Wanneer een element van een lijst, matrix of reeksexpressie een bekend doeltype heeft.

Denk bijvoorbeeld aan de volgende 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 de vertakkingen van de voorwaardelijke berekening een TextReader en StreamReader respectievelijk. Op de tweede vertakking is TextReader het bekende doeltype afkomstig van de typeaantekening voor de methode en van de eerste vertakking. Dit betekent dat er geen upcast nodig is voor de tweede vertakking.

Als u een waarschuwing wilt weergeven op elk punt dat een extra impliciete upcast wordt gebruikt, kunt u waarschuwing 3388 (/warnon:3388 of eigenschap <WarnOn>3388</WarnOn>) inschakelen.

Impliciete numerieke conversies

F# maakt gebruik van expliciete verbreiding van numerieke typen in de meeste gevallen via conversieoperators. Expliciete widening is bijvoorbeeld nodig voor de meeste numerieke typen, zoals int8 of int16, of van float32 naar float64, of wanneer bron- of doeltype onbekend is.

Impliciete widening is echter toegestaan voor 32-bits gehele getallen die worden uitgebreid tot 64-bits gehele getallen, in dezelfde situaties als impliciete upcasts. Denk bijvoorbeeld aan een typische API-shape:

type Tensor(…) =
    static member Create(sizes: seq<int64>) = Tensor(…)

Letterlijke waarden voor gehele getallen voor int64 kunnen worden gebruikt:

Tensor.Create([100L; 10L; 10L])

Of letterlijke gehele getallen voor int32:

Tensor.Create([int64 100; int64 10; int64 10])

Het breder maken gebeurt automatisch voorint32, int32 tot nativeintint64en int32 metdouble, wanneer zowel het bron- als het doeltype bekend zijn tijdens typedeductie. In gevallen zoals de vorige voorbeelden int32 kunnen letterlijke waarden dus worden gebruikt:

Tensor.Create([100; 10; 10])

U kunt de waarschuwing 3389 (/warnon:3389 of eigenschap <WarnOn>3389</WarnOn>) desgewenst ook inschakelen om een waarschuwing weer te geven op elk punt dat impliciete numerieke widening wordt gebruikt.

. Impliciete conversies in NET-stijl

Met .NET-API's kan de definitie van op_Implicit statische methoden impliciete conversies tussen typen bieden. Deze worden automatisch toegepast in F#-code bij het doorgeven van argumenten aan methoden. Denk bijvoorbeeld aan de volgende code die expliciete aanroepen naar methoden doet op_Implicit :

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants(XName.op_Implicit "Item")

. Conversies in NET-stijl op_Implicit worden automatisch toegepast voor argumentexpressies wanneer typen beschikbaar zijn voor bronexpressie en doeltype:

open System.Xml.Linq

let purchaseOrder = XElement.Load("PurchaseOrder.xml")
let partNos = purchaseOrder.Descendants("Item")

U kunt de waarschuwing 3395 (/warnon:3395 of eigenschap <WarnOn>3395</WarnOn>) desgewenst ook inschakelen om op elk punt een waarschuwing weer te geven. Impliciete conversie in NET-stijl wordt gebruikt.

. Net-stijlconversies op_Implicit worden ook automatisch toegepast op expressies zonder methode-argument in dezelfde situaties als impliciete upcasts. Wanneer impliciete conversies echter veel of ongepast worden gebruikt, kunnen impliciete conversies slecht communiceren met typedeductie en leiden tot code die moeilijker te begrijpen is. Daarom genereren deze altijd waarschuwingen wanneer ze worden gebruikt in niet-argumentposities.

Een waarschuwing weergeven op elk punt dat een . Impliciete conversie in NET-stijl wordt gebruikt voor een argument dat niet de methode is, u kunt waarschuwing 3391 (/warnon:3391 of eigenschap <WarnOn>3391</WarnOn>) inschakelen.

De volgende optionele waarschuwingen worden geboden voor het gebruik van impliciete conversies:

  • /warnon:3388 (aanvullende impliciete upcast)
  • /warnon:3389 (impliciete numerieke widening)
  • /warnon:3391 (op_Implicit bij niet-methodeargumenten, standaard ingeschakeld)
  • /warnon:3395 (op_Implicit bij methodeargumenten)

Zie ook