Compartilhar via


Conversões 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 verificaram e desmarcaram formulários; os operadores de ponto flutuante e o enum operador de conversão não. Os formulários desmarcados são definidos FSharp.Core.Operators e os formulários verificados são definidos em FSharp.Core.Operators.Checked. Os formulários verificados verificam o estouro e geram uma exceção de runtime se o valor resultante exceder os limites do tipo de destino.

Cada um desses operadores tem o mesmo 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 assinado.
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 única de 32 bits.
decimal Converter em System.Decimal.
char Converter em System.Charum caractere Unicode.
enum Converter em um tipo enumerado.

Além dos tipos primitivos internos, você pode usar esses operadores com tipos que implementam op_Explicit ou op_Implicit métodos com assinaturas apropriadas. Por exemplo, o int operador de conversão funciona com qualquer tipo que fornece um método op_Explicit estático 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 por tipo de retorno, você pode fazer isso para op_Explicit e op_Implicit.

Tipos enumerados

O enum operador é um operador genérico que usa um parâmetro de tipo que representa o tipo do para o enum qual converter. Quando ele é convertido em um tipo enumerado, a inferência de tipo tenta determinar o tipo no enum 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 Color enumeração. 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 for compatível com o tipo que está sendo convertido. No código a seguir, a conversão falha ao compilar devido à incompatibilidade entre int32 e uint32.

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

Para obter mais informações, consulte 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 objetos. Há dois tipos básicos de conversões: conversão (upcasting) e 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. Essa conversão é garantida para funcionar desde que a classe base esteja 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 e o :?> operador converte a hierarquia.

Upcasting

Em muitas linguagens orientadas a objetos, o upcasting é implícito; em F#, as regras são ligeiramente 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 obter mais informações, consulte Tipos Flexíveis.

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

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

upcast expression

Quando você usa o operador upcast, o compilador tenta inferir o tipo no 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; mas, em tempo de execução, é feita uma tentativa de conversão para o 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 downcast operador para executar uma conversão de tipo dinâmico. A expressão a seguir especifica uma conversão na hierarquia para um tipo que é inferido do contexto do programa:

downcast expression

Quanto ao upcast operador, 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 :?> operadores. O código ilustra que o :?> operador é melhor usado quando você sabe que a conversão será bem-sucedida, pois ela será gerada InvalidCastException se a conversão falhar. Se você não souber que uma conversão será bem-sucedida, um teste de tipo que usa uma match expressão é 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 tipo 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 foi possível 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 if/then/else ou match expressão 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 da computação condicional são a 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 na segunda ramificação.

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 int32int64, int32 para nativeinte int32 para double, quando o tipo de origem e de destino são conhecidos durante a inferência de tipo. Portanto, em casos como os exemplos anteriores, int32 literais 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 op_Implicit métodos estáticos 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 código a seguir fazendo chamadas explícitas aos op_Implicit métodos:

open System.Xml.Linq

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

. As conversões de estilo op_Implicit 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 de um . A conversão implícita no estilo NET é usada.

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

Para mostrar um aviso em todos os pontos que um . A conversão implícita no estilo NET é usada para um argumento não 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)

Consulte também