Cast et conversions (F#)

Cet article décrit la prise en charge des conversions de type en F#.

Types arithmétiques

F# fournit des opérateurs de conversion pour les conversions arithmétiques entre différents types primitifs, par exemple entre le type entier et le type à virgule flottante. Les opérateurs de conversion du type intégral et du type caractères se présentent sous une forme vérifiée et une forme non vérifiée, ce qui n’est pas le cas des opérateurs à virgule flottante ni de l’opérateur de conversion enum. Les formes non vérifiées sont définies dans FSharp.Core.Operators, les formes vérifiées dans FSharp.Core.Operators.Checked. Les formes vérifiées contrôlent le dépassement de capacité et génèrent une exception d’exécution si la valeur résultante dépasse les limites du type cible.

Chacun de ces opérateurs porte le même nom que le type de destination. Par exemple, dans le code suivant, où les types sont explicitement annotés, byte apparaît avec deux significations différentes. La première occurrence est le type, la seconde l’opérateur de conversion.

let x : int = 5

let b : byte = byte x

Le tableau suivant montre les opérateurs de conversion définis en F#.

Opérateur Description
byte Convertir en octet, un type non signé 8 bits.
sbyte Convertir en octet signé.
int16 Convertir en entier signé 16 bits.
uint16 Convertir en entier non signé 16 bits.
int32, int Convertir en entier signé 32 bits.
uint32 Convertir en entier non signé 32 bits.
int64 Convertir en entier signé 64 bits.
uint64 Convertir en entier non signé 64 bits.
nativeint Convertir en entier natif.
unativeint Convertir en entier non signé natif.
float, double Convertir en nombre à virgule flottante IEEE double précision 64 bits.
float32, single Convertir en nombre à virgule flottante IEEE simple précision 32 bits.
decimal Convertir en System.Decimal.
char Convertir en System.Char, un caractère Unicode.
enum Convertir en type énuméré.

En plus des types primitifs intégrés, vous pouvez utiliser ces opérateurs avec des types qui implémentent des méthodes op_Explicit ou op_Implicit avec des signatures appropriées. Par exemple, l’opérateur de conversion int fonctionne avec n’importe quel type qui fournit une méthode statique op_Explicit prenant le type comme paramètre et retournant int. À titre d’exception spéciale à la règle générale, les méthodes op_Explicit et op_Implicit peuvent être surchargées par le type de retour.

Types énumérés

L’opérateur enum est un opérateur générique qui prend un paramètre de type représentant le type de enum vers lequel la conversion sera effectuée. Dans le cas d’une conversion en type énuméré, l’inférence de type tente de déterminer le type d’enum souhaité. Dans l’exemple suivant, la variable col1 n’est pas explicitement annotée, mais son type est déduit du test d’égalité ultérieur. Le compilateur peut ainsi conclure qu’il s’agit d’une conversion en énumération Color. Vous pouvez également fournir une annotation de type, comme col2 dans l’exemple suivant.

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

Vous pouvez également spécifier explicitement le type d’énumération cible en tant que paramètre de type, comme dans le code suivant :

let col3 = enum<Color> 3

Notez que les casts d’énumération ne fonctionnent que si le type sous-jacent de l’énumération est compatible avec le type en cours de conversion. Dans le code suivant, la conversion ne se compile pas en raison de l’incompatibilité entre int32 et uint32.

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

Pour plus d’informations, consultez Énumérations.

Types d’objets de casting

La conversion entre les types dans une hiérarchie d’objets est fondamentale pour la programmation orientée objet. Il existe deux types de conversions de base : la conversion vers le haut (upcast) et la conversion vers le bas (downcast). La conversion vers le haut dans une hiérarchie part d’une référence d’objet dérivée pour aller vers une référence d’objet de base. Un tel cast est garanti tant que la classe de base se trouve dans la hiérarchie d’héritage de la classe dérivée. La conversion vers le bas, d’une référence d’objet de base à une référence d’objet dérivée, ne réussit que si l’objet constitue en fait une instance du type de destination (dérivé) correct ou un type dérivé du type de destination.

F# fournit des opérateurs pour ces types de conversions. L’opérateur :> permet de caster vers le haut de la hiérarchie, l’opérateur :?> vers le bas.

Upcast

Dans de nombreux langages orientés objet, l’upcast est implicite. En F#, les règles sont légèrement différentes. L’upcast est appliqué automatiquement lorsque l’on passe des arguments à des méthodes sur un type d’objet. Toutefois, pour les fonctions limitées par let dans un module, il n’est pas automatique, à moins que le type de paramètre ne soit déclaré en tant que type flexible. Pour plus d’informations, consultez Types flexibles.

L’opérateur :> effectue un cast statique, ce qui signifie que la réussite du cast est déterminée au moment de la compilation. Un cast utilisant :> dont la compilation réussit constitue un cast valide qui ne présente aucun risque d’échec à l’exécution.

Vous pouvez également utiliser l’opérateur upcast pour effectuer une telle conversion. L’expression suivante spécifie une conversion vers le haut de la hiérarchie :

upcast expression

Lorsque vous utilisez l’opérateur upcast, le compilateur tente d’inférer à partir du contexte le type vers lequel vous effectuez la conversion. S’il ne parvient pas à déterminer le type cible, il signale une erreur. Une annotation de type peut être requise.

Downcast

L’opérateur :?> effectue un cast dynamique, ce qui signifie que la réussite du cast est déterminée au moment de l’exécution. Un cast qui utilise l’opérateur :?> n’est pas vérifié à la compilation. À l’exécution toutefois, une tentative de conversion est effectuée vers le type spécifié. Si l’objet est compatible avec le type cible, le cast réussit. Dans le cas contraire, l’exécution déclenche une InvalidCastException.

Vous pouvez également utiliser l’opérateur downcast pour effectuer une conversion de type dynamique. L’expression suivante spécifie une conversion vers le bas de la hiérarchie en un type déduit du contexte du programme :

downcast expression

Comme pour l’opérateur upcast, si le compilateur ne peut pas déduire un type cible spécifique du contexte, il signale une erreur. Une annotation de type peut être requise.

Le code suivant illustre l’utilisation des opérateurs :> et :?>. Il montre que l’opérateur :?> est plus efficace lorsque l’on a la certitude que la conversion réussira, car il lève InvalidCastException en cas d’échec. Si vous ne savez pas si une conversion aboutira, il est préférable d’avoir recours à un test de type qui utilise une expression match, car cela évite la surcharge liée à la génération d’une exception.

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

Étant donné que les opérateurs génériques downcast et upcast s’appuient sur l’inférence de type pour déterminer l’argument et le type de retour, vous pouvez remplacer let base1 = d1 :> Base1 par let base1: Base1 = upcast d1 dans l’exemple de code précédent.

Une annotation de type est requise, car upcast n’a pas pu à lui seul déterminer la classe de base.

Conversions de type upcast implicite

Les upcasts implicites sont insérés dans les situations suivantes :

  • Un paramètre est fourni à une fonction ou à une méthode avec un type nommé connu. Par exemple, une construction telle qu’une expression de calcul ou un découpage devient un appel de méthode.

  • Un champ ou une propriété d’enregistrement doté d’un type nommé connu est assigné ou muté.

  • Une branche d’une expression if/then/else ou match possède un type cible connu provenant d’une autre branche ou d’un type connu global.

  • Un élément d’une expression de liste, de tableau ou de séquence possède un type cible connu.

Considérons par exemple le code suivant :

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")

Les branches du calcul conditionnel sont respectivement TextReader et StreamReader. Sur la seconde branche, le type cible connu est TextReader d’après l’annotation de type de la méthode et d’après la première branche. Cela signifie qu’aucun upcast n’est nécessaire sur la deuxième branche.

Pour afficher un avertissement à chaque point où un upcast implicite supplémentaire est utilisé, vous pouvez activer l’avertissement 3388 (/warnon:3388 ou la propriété <WarnOn>3388</WarnOn>).

Conversions numériques implicites

F# utilise dans la plupart des cas la conversion étendue explicite des types numériques au moyen d’opérateurs de conversion. Par exemple, la conversion étendue implicite est nécessaire pour la plupart des types numériques (notamment de int8 à int16 et de float32 à float64) et lorsque le type de la source ou de la destination est inconnu.

Toutefois, la conversion étendue implicite est autorisée pour les entiers 32 bits étendus à des entiers 64 bits, dans les mêmes situations que les upcasts implicites. Par exemple, examinons une forme d’API classique :

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

Les littéraux entiers peuvent être utilisés pour Int64 :

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

De même, les littéraux entiers peuvent être utilisés pour Int32 :

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

La conversion étendue se produit automatiquement de int32 à int64, de int32 à nativeint et de int32 à double, lorsque le type de la source et celui de la destination sont connus pendant l’inférence de type. Ainsi, dans les cas tels que les exemples précédents, les littéraux int32 peuvent être utilisés :

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

Vous pouvez également, si vous le souhaitez, activer l’avertissement 3389 (/warnon:3389 ou la propriété <WarnOn>3389</WarnOn>) pour afficher un avertissement à chaque point où une conversion étendue numérique implicite est utilisée.

Conversions implicites de style .NET

Les API .NET permettent la définition de méthodes statiques op_Implicit pour fournir des conversions implicites entre les types. Celles-ci sont appliquées automatiquement dans le code F# lors du passage d’arguments à des méthodes. Prenons par exemple le code suivant qui effectue des appels explicites aux méthodes op_Implicit :

open System.Xml.Linq

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

Les conversions op_Implicit de style .NET sont appliquées automatiquement pour les expressions d’arguments lorsque les types sont disponibles pour l’expression source et le type cible :

open System.Xml.Linq

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

Vous pouvez également, si vous le souhaitez, activer l’avertissement 3395 (/warnon:3395 ou la propriété <WarnOn>3395</WarnOn>) pour afficher un avertissement à chaque point où une conversion implicite de style .NET est utilisée.

Les conversions op_Implicit de style .NET sont également appliquées automatiquement pour les expressions d’arguments hors méthode, dans les mêmes situations que les upcasts implicites. Cependant, lorsqu’elles sont utilisées à grande échelle ou de manière inappropriée, les conversions implicites peuvent mal interagir avec l’inférence de type et donner un code plus difficile à comprendre. C’est la raison pour laquelle elles génèrent toujours des avertissements lorsqu’elles sont utilisées dans des positions dépourvues d’arguments.

Pour afficher un avertissement à chaque point où une conversion implicite de style .NET est utilisée pour un argument hors méthode, vous pouvez activer l’avertissement 3391 (/warnon:3391 ou la propriété <WarnOn>3391</WarnOn>).

Les avertissements facultatifs fournis pour les utilisations de conversions implicites sont les suivants :

  • /warnon:3388 (upcast implicite supplémentaire)
  • /warnon:3389 (élargissement numérique implicite)
  • /warnon:3391 (op_Implicit aux arguments hors méthode, activé par défaut)
  • /warnon:3395 (op_Implicit aux arguments de méthode)

Voir aussi