Conversões cast e conversões (F#)

Este artigo descreve o suporte para conversões de tipo em F#.

Tipos aritméticos

O F# fornece operadores de conversão para conversões aritméticas entre vários tipos primitivos, como entre tipos inteiros e de ponto flutuante. Os operadores de conversão integral e char têm formulários verificados e não verificados; os operadores de ponto flutuante e o operador de conversão enum, não. Os formulários desmarcados são definidos em FSharp.Core.Operators e os formulários verificados são definidos em FSharp.Core.Operators.Checked. Os formulários verificados analisam o estouro e geram uma exceção de runtime se o valor resultante excede os limites do tipo de destino.

Cada um desses operadores tem o mesmo nome que o nome do tipo de destino. Por exemplo, no código a seguir, no qual os tipos são explicitamente anotados, byte aparece com dois significados diferentes. A primeira ocorrência é o tipo e a segunda, o operador de conversão.

let x : int = 5

let b : byte = byte x

A tabela a seguir mostra os operadores de conversão definidos em F#.

Operador Descrição
byte Converter em byte, um tipo sem sinal de 8 bits.
sbyte Converter em byte com sinal.
int16 Converter em um inteiro com sinal de 16 bits.
uint16 Converter em um inteiro sem sinal de 16 bits.
int32, int Converter em um inteiro com sinal de 32 bits.
uint32 Converter em um inteiro sem sinal de 32 bits.
int64 Converter em um inteiro com sinal de 64 bits.
uint64 Converter em um inteiro sem sinal de 64 bits.
nativeint Converter em um inteiro nativo.
unativeint Converter em um inteiro nativo sem sinal.
float, double Converter em um número de ponto flutuante IEEE de precisão dupla de 64 bits.
float32, single Converter em um número de ponto flutuante IEEE de precisão simples de 32 bits.
decimal Converter em System.Decimal.
char Converter em System.Char, um caractere Unicode.
enum Converter em um tipo enumerado.

Além de tipos primitivos internos, você pode usar esses operadores com tipos que implementam métodos op_Explicit ou op_Implicit com assinaturas apropriadas. Por exemplo, o operador de conversão intfunciona com qualquer tipo que fornece um método estático op_Explicit que usa o tipo como um parâmetro e retorna int. Como uma exceção especial à regra geral de que os métodos não podem ser sobrecarregados pelo tipo de retorno, você pode fazer isso para op_Explicite op_Implicit.

Tipos enumerados

O operador enum é um operador genérico que usa um parâmetro de tipo que representa o tipo do enum no qual converter. Quando ele é convertido em um tipo enumerado, a inferência de tipos tenta determinar o tipo do enum no qual você deseja converter. No exemplo a seguir, a variável col1 não é explicitamente anotada, mas seu tipo é inferido do teste de igualdade posterior. Portanto, o compilador pode deduzir que você está convertendo em uma enumeração Color. Como alternativa, você pode fornecer uma anotação de tipo, como col2 no exemplo a seguir.

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

Você também pode especificar o tipo de enumeração de destino explicitamente como um parâmetro de tipo, como no seguinte código:

let col3 = enum<Color> 3

Observe que as conversões de enumeração funcionam somente se o tipo subjacente da enumeração é compatível com o tipo que está sendo convertido. No código a seguir, a conversão não é compilada devido à incompatibilidade entre int32 e uint32.

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

Para mais informações, confira Enumerações.

Tipos de objeto de conversão

A conversão entre tipos em uma hierarquia de objetos é fundamental para a programação orientada a objeto. Há dois tipos básicos de conversões: a conversão para cima (upcasting) e a conversão para baixo (downcasting). A conversão de uma hierarquia significa a conversão de uma referência de objeto derivado para uma referência de objeto base. É garantido que essa conversão funcionará enquanto a classe base estiver na hierarquia de herança da classe derivada. A conversão de uma hierarquia, de uma referência de objeto base a uma referência de objeto derivado, só terá êxito se o objeto for realmente uma instância do tipo de destino correto (derivado) ou um tipo derivado do tipo de destino.

O F# fornece operadores para esses tipos de conversões. O operador :> converte a hierarquia para cima e o operador :?> converte a hierarquia para baixo.

Upcasting

Em muitas linguagens orientadas a objetos, o upcasting é implícito; em F#, as regras são levemente diferentes. O upcasting é aplicado automaticamente quando você passa argumentos para métodos em um tipo de objeto. No entanto, para funções let-bound em um módulo, o upcasting não é automático, a menos que o tipo de parâmetro seja declarado como um tipo flexível. Para mais informações, confira Tipos flexíveis.

O operador :> executa uma conversão estática, o que significa que o sucesso da conversão é determinado no tempo de compilação. Se uma conversão que usa :> fizer a compilação com êxito, ela será uma conversão válida e não terá nenhuma chance de falha em tempo de execução.

Você também pode usar o operador upcast para executar essa conversão. A seguinte expressão especifica uma conversão na hierarquia:

upcast expression

Quando você usa o operador upcast, o compilador tenta inferir o tipo ao qual você está convertendo do contexto. Se o compilador não conseguir determinar o tipo de destino, o compilador relatará um erro. Uma anotação de tipo pode ser necessária.

Downcasting

O operador :?> executa uma conversão dinâmica, o que significa que o sucesso do elenco é determinado em tempo de execução. Uma conversão que usa o operador :?> não é verificada em tempo de compilação; porém, em tempo de execução, é feita uma tentativa de conversão no tipo especificado. Se o objeto for compatível com o tipo de destino, a conversão será bem-sucedida. Se o objeto não for compatível com o tipo de destino, o runtime gerará um InvalidCastException.

Você também pode usar o operador downcast para executar uma conversão de tipo dinâmico. A seguinte expressão especifica uma conversão na hierarquia para um tipo que é inferido do contexto do programa:

downcast expression

Quanto ao operador upcast, se o compilador não puder inferir um tipo de destino específico do contexto, ele relatará um erro. Uma anotação de tipo pode ser necessária.

O código a seguir ilustra o uso de operadores :> e :?>. O código ilustra que o operador :?> é melhor usado quando você sabe que a conversão terá êxito, pois ela vai gerar InvalidCastException se a conversão falhar. Se você não souber que uma conversão terá êxito, um teste de tipo que usa uma expressão match será melhor porque evita a sobrecarga de gerar uma exceção.

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

Como os operadores genéricos downcast e upcast dependem da inferência de tipos para determinar o argumento e o tipo de retorno, você pode substituir let base1 = d1 :> Base1 no exemplo de código anterior por let base1: Base1 = upcast d1.

Uma anotação de tipo é necessária, porque upcast por si só não conseguiu determinar a classe base.

Conversões de upcast implícitas

Os upcasts implícitos são inseridos nas seguintes situações:

  • Ao fornecer um parâmetro a uma função ou método com um tipo nomeado conhecido. Isso inclui quando um constructo como expressões de computação ou fatiamento se torna uma chamada de método.

  • Ao atribuir ou alterar um campo de registro ou propriedade que tenha um tipo nomeado conhecido.

  • Quando um branch de uma expressão if/then/else ou match tem um tipo de destino conhecido decorrente de outro branch ou tipo conhecido geral.

  • Quando um elemento de uma lista, matriz ou expressão de sequência tem um tipo de destino conhecido.

Por exemplo, considere o seguinte código:

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

Aqui, os branches do condicional computam TextReader e StreamReader, respectivamente. No segundo branch, o tipo de destino conhecido é TextReader da anotação de tipo no método e do primeiro branch. Isso significa que nenhum upcast é necessário no segundo branch.

Para mostrar um aviso em cada ponto em que um upcast implícito adicional é usado, você pode habilitar o aviso 3388 (/warnon:3388 ou propriedade <WarnOn>3388</WarnOn>).

Conversões numéricas implícitas

O F# usa a ampliação explícita de tipos numéricos na maioria dos casos por meio de operadores de conversão. Por exemplo, a ampliação explícita é necessária para a maioria dos tipos numéricos, como int8 ou int16, ou de float32 para float64, ou quando o tipo de origem ou destino é desconhecido.

No entanto, a ampliação implícita é permitida para inteiros de 32 bits ampliados para inteiros de 64 bits, nas mesmas situações que os upcasts implícitos. Por exemplo, considere uma forma de API típica:

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

Literais inteiros para int64 podem ser usados:

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

Ou literais inteiros para int32:

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

A ampliação ocorre automaticamente para int32 para int64, para int32 para nativeint e para int32 para double quando o tipo de origem e de destino são conhecidos durante a inferência de tipos. Portanto, em casos como os exemplos anteriores, literais int32 podem ser usados:

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

Opcionalmente, você também pode habilitar o aviso 3389 (/warnon:3389 ou propriedade <WarnOn>3389</WarnOn>) para mostrar um aviso em cada ponto em que a ampliação numérica implícita é usada.

Conversões implícitas no estilo .NET

As APIs do .NET permitem que a definição de métodos estáticos op_Implicit forneça conversões implícitas entre tipos. Elas são aplicadas automaticamente no código F# ao passar argumentos para métodos. Por exemplo, considere o seguinte código fazendo chamadas explícitas aos métodos op_Implicit:

open System.Xml.Linq

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

As conversões op_Implicit no estilo .NET são aplicadas automaticamente para expressões de argumento quando os tipos estão disponíveis para expressão de origem e tipo de destino:

open System.Xml.Linq

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

Opcionalmente, você também pode habilitar o aviso 3395 (/warnon:3395 ou propriedade <WarnOn>3395</WarnOn>) para mostrar um aviso em cada ponto em que a conversão implícita no estilo .NET é usada.

As conversões op_Implicit no estilo .NET também são aplicadas automaticamente para expressões de argumento não de método nas mesmas situações que os upcasts implícitos. No entanto, quando usadas de maneira ampla ou inadequada, as conversões implícitas podem interagir mal com a inferência de tipos e levar a um código mais difícil de entender. Por esse motivo, elas sempre geram avisos quando usadas em posições que não são de argumento.

Para mostrar um aviso em cada ponto que uma conversão implícita no estilo .NET é usada para um argumento não de método, você pode habilitar o aviso 3391 (/warnon:3391 ou propriedade <WarnOn>3391</WarnOn>).

Os seguintes avisos opcionais são fornecidos para usos de conversões implícitas:

  • /warnon:3388 (upcast implícito adicional)
  • /warnon:3389 (ampliação numérica implícita)
  • /warnon:3391 (op_Implicit em argumentos que não são de método, ativados por padrão)
  • /warnon:3395 (op_Implicit em argumentos de método)

Confira também