Cast e conversioni (F#)

Questo articolo descrive il supporto per le conversioni dei tipi in F#.

Tipi aritmetici

F# fornisce operatori di conversione per conversioni aritmetiche tra vari tipi primitivi, ad esempio tra tipi integer e a virgola mobile. Gli operatori di conversione integrali e char sono formati checked e unchecked; gli operatori a virgola mobile e l'operatore enum di conversione non lo fanno. I moduli non controllati sono definiti in FSharp.Core.Operators e i moduli controllati sono definiti in FSharp.Core.Operators.Checked. I moduli controllati controllano la presenza di overflow e generano un'eccezione di runtime se il valore risultante supera i limiti del tipo di destinazione.

Ognuno di questi operatori ha lo stesso nome del nome del tipo di destinazione. Ad esempio, nel codice seguente, in cui i tipi vengono annotati in modo esplicito, byte vengono visualizzati con due significati diversi. La prima occorrenza è il tipo e la seconda è l'operatore di conversione.

let x : int = 5

let b : byte = byte x

La tabella seguente illustra gli operatori di conversione definiti in F#.

Operator Descrizione
byte Converti in byte, un tipo senza segno a 8 bit.
sbyte Converti in byte firmato.
int16 Convertire in un intero con segno a 16 bit.
uint16 Convertire in un intero senza segno a 16 bit.
int32, int Convertire in un intero con segno a 32 bit.
uint32 Convertire in un intero senza segno a 32 bit.
int64 Convertire in un intero con segno a 64 bit.
uint64 Convertire in un intero senza segno a 64 bit.
nativeint Convertire in un numero intero nativo.
unativeint Convertire in un intero nativo senza segno.
float, double Convertire in un numero a virgola mobile I edizione Enterprise E a precisione doppia a 64 bit.
float32, single Convertire in un numero a virgola mobile I edizione Enterprise E a precisione singola a 32 bit.
decimal Convertire in System.Decimal.
char Convertire in System.Char, un carattere Unicode.
enum Convertire in un tipo enumerato.

Oltre ai tipi primitivi predefiniti, è possibile usare questi operatori con tipi che implementano op_Explicit o op_Implicit metodi con firme appropriate. Ad esempio, l'operatore int di conversione funziona con qualsiasi tipo che fornisce un metodo op_Explicit statico che accetta il tipo come parametro e restituisce int. Come eccezione speciale alla regola generale che i metodi non possono essere sovraccaricati dal tipo restituito, è possibile eseguire questa operazione per op_Explicit e op_Implicit.

Tipi enumerati

L'operatore enum è un operatore generico che accetta un parametro di tipo che rappresenta il tipo dell'oggetto in cui eseguire la enum conversione. Quando si converte in un tipo enumerato, l'inferenza del tipo tenta di determinare il tipo dell'oggetto enum in cui si desidera eseguire la conversione. Nell'esempio seguente la variabile col1 non viene annotata in modo esplicito, ma il relativo tipo viene dedotto dal test di uguaglianza successivo. Pertanto, il compilatore può dedurre che si sta convertendo in un'enumerazione Color . In alternativa, è possibile specificare un'annotazione di tipo, come col2 nell'esempio seguente.

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

È anche possibile specificare il tipo di enumerazione di destinazione in modo esplicito come parametro di tipo, come nel codice seguente:

let col3 = enum<Color> 3

Si noti che il cast dell'enumerazione funziona solo se il tipo sottostante dell'enumerazione è compatibile con il tipo convertito. Nel codice seguente la conversione non viene compilata a causa della mancata corrispondenza tra int32 e uint32.

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

Per altre informazioni, vedere Enumerazioni.

Cast di tipi di oggetto

La conversione tra tipi in una gerarchia di oggetti è fondamentale per la programmazione orientata agli oggetti. Esistono due tipi di conversioni di base: cast up (upcasting) e cast down (downcasting). Eseguire il cast di una gerarchia significa eseguire il cast da un riferimento a un oggetto derivato a un riferimento all'oggetto di base. Tale cast è garantito che funzioni purché la classe di base si trova nella gerarchia di ereditarietà della classe derivata. Il cast di una gerarchia, da un riferimento a un oggetto di base a un riferimento a un oggetto derivato, ha esito positivo solo se l'oggetto è effettivamente un'istanza del tipo di destinazione corretto (derivato) o un tipo derivato dal tipo di destinazione.

F# fornisce operatori per questi tipi di conversioni. L'operatore :> esegue il cast della gerarchia e l'operatore esegue il :?> cast nella gerarchia.

Upcasting

In molti linguaggi orientati agli oggetti, l'upcasting è implicito; in F#, le regole sono leggermente diverse. L'upcast viene applicato automaticamente quando si passano argomenti a metodi su un tipo di oggetto. Tuttavia, per le funzioni associate a let in un modulo, l'upcasting non è automatico, a meno che il tipo di parametro non sia dichiarato come tipo flessibile. Per altre informazioni, vedere Tipi flessibili.

L'operatore :> esegue un cast statico, ovvero l'esito positivo del cast viene determinato in fase di compilazione. Se un cast che usa :> la compilazione correttamente, si tratta di un cast valido e non ha alcuna probabilità di errore in fase di esecuzione.

È anche possibile usare l'operatore upcast per eseguire tale conversione. L'espressione seguente specifica una conversione nella gerarchia:

upcast expression

Quando si usa l'operatore upcast, il compilatore tenta di dedurre il tipo in cui si esegue la conversione dal contesto. Se il compilatore non è in grado di determinare il tipo di destinazione, il compilatore segnala un errore. È possibile che sia necessaria un'annotazione di tipo.

Downcasting

L'operatore :?> esegue un cast dinamico, ovvero l'esito positivo del cast viene determinato in fase di esecuzione. Un cast che usa l'operatore :?> non viene controllato in fase di compilazione, ma in fase di esecuzione viene effettuato un tentativo di eseguire il cast al tipo specificato. Se l'oggetto è compatibile con il tipo di destinazione, il cast ha esito positivo. Se l'oggetto non è compatibile con il tipo di destinazione, il runtime genera un oggetto InvalidCastException.

È anche possibile usare l'operatore downcast per eseguire una conversione di tipo dinamico. L'espressione seguente specifica una conversione verso il basso nella gerarchia in un tipo dedotto dal contesto del programma:

downcast expression

Per quanto riguarda l'operatore upcast , se il compilatore non può dedurre un tipo di destinazione specifico dal contesto, segnala un errore. È possibile che sia necessaria un'annotazione di tipo.

Il codice seguente illustra l'uso degli :> operatori e :?> . Il codice illustra che l'operatore :?> è meglio usato quando si sa che la conversione avrà esito positivo, perché genera un'eccezione InvalidCastException se la conversione non riesce. Se non si sa che una conversione avrà esito positivo, un test di tipo che usa un'espressione match è migliore perché evita il sovraccarico della generazione di un'eccezione.

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

Poiché gli operatori generici downcast e upcast si basano sull'inferenza del tipo per determinare l'argomento e il tipo restituito, è possibile sostituire let base1 = d1 :> Base1 nell'esempio di codice precedente con let base1: Base1 = upcast d1.

È necessaria un'annotazione del tipo, perché upcast da sola non è stato possibile determinare la classe di base.

Conversioni di upcast implicite

I upcast impliciti vengono inseriti nelle situazioni seguenti:

  • Quando si specifica un parametro a una funzione o a un metodo con un tipo denominato noto. Ciò include quando un costrutto, ad esempio espressioni di calcolo o sezionamento, diventa una chiamata al metodo.

  • Quando si assegna o si modifica un campo o una proprietà di record con un tipo denominato noto.

  • Quando un ramo di un'espressione if/then/else o match ha un tipo di destinazione noto derivante da un altro ramo o da un altro tipo noto complessivo.

  • Quando un elemento di un'espressione elenco, matrice o sequenza ha un tipo di destinazione noto.

Si consideri il codice di esempio seguente:

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

Qui i rami del calcolo condizionale a TextReader e StreamReader rispettivamente. Nel secondo ramo il tipo di destinazione noto proviene TextReader dall'annotazione del tipo nel metodo e dal primo ramo. Ciò significa che non è necessario alcun upcast nel secondo ramo.

Per visualizzare un avviso in ogni momento viene usato un upcast implicito aggiuntivo, è possibile abilitare l'avviso 3388 (/warnon:3388 o la proprietà <WarnOn>3388</WarnOn>).

Conversioni numeriche implicite

F# usa l'ampliamento esplicito dei tipi numerici nella maggior parte dei casi tramite operatori di conversione. Ad esempio, per la maggior parte dei tipi numerici, ad int8 esempio o int16, o da float32 a float64, o quando il tipo di origine o di destinazione è sconosciuto.

Tuttavia, l'ampliamento implicito è consentito per interi a 32 bit ampliati a interi a 64 bit, nelle stesse situazioni dei upcast impliciti. Si consideri ad esempio una forma API tipica:

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

I valori letterali integer per int64 possono essere usati:

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

Valori letterali integer per int32:

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

L'estensione avviene automaticamente per int32 a int64, int32 a nativeinte int32 a double, quando il tipo di origine e di destinazione è noto durante l'inferenza del tipo. Pertanto, nei casi come gli esempi precedenti, int32 è possibile usare i valori letterali:

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

Facoltativamente, è anche possibile abilitare l'avviso 3389 (/warnon:3389 o la proprietà <WarnOn>3389</WarnOn>) per visualizzare un avviso in ogni punto viene usato l'ampliamento numerico implicito.

. Conversioni implicite in stile NET

Le API .NET consentono la definizione di op_Implicit metodi statici per fornire conversioni implicite tra tipi. Questi vengono applicati automaticamente nel codice F# quando si passano argomenti ai metodi. Si consideri ad esempio il codice seguente che effettua chiamate esplicite ai op_Implicit metodi:

open System.Xml.Linq

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

. Le conversioni in stile op_Implicit NET vengono applicate automaticamente per le espressioni di argomento quando i tipi sono disponibili per l'espressione di origine e il tipo di destinazione:

open System.Xml.Linq

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

Facoltativamente, è anche possibile abilitare l'avviso 3395 (/warnon:3395 o la proprietà <WarnOn>3395</WarnOn>) per visualizzare un avviso in ogni punto di un oggetto . Viene usata la conversione implicita in stile NET.

. Le conversioni in stile op_Implicit NET vengono applicate automaticamente anche per le espressioni non di argomento metodo nelle stesse situazioni dei upcast impliciti. Tuttavia, se usate in modo ampiamente o inappropriato, le conversioni implicite possono interagire in modo non corretto con l'inferenza del tipo e portare a codice più difficile da comprendere. Per questo motivo, questi generano sempre avvisi quando vengono usati in posizioni non di argomento.

Per visualizzare un avviso in ogni punto in cui un oggetto . La conversione implicita in stile NET viene usata per un argomento non metodo, è possibile abilitare l'avviso 3391 (/warnon:3391 o la proprietà <WarnOn>3391</WarnOn>).

Per gli usi di conversioni implicite vengono forniti gli avvisi facoltativi seguenti:

  • /warnon:3388 (upcast implicito aggiuntivo)
  • /warnon:3389 (ampliamento numerico implicito)
  • /warnon:3391 (op_Implicit per impostazione predefinita, argomenti non di metodo)
  • /warnon:3395 (op_Implicit agli argomenti del metodo)

Vedi anche