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
oumatch
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>
).
Résumé des avertissements liés aux conversions
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)