Partilhar via


Fundição e conversões (F#)

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

Tipos Aritméticos

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 enum conversão não. Os formulários não verificados são definidos em FSharp.Core.Operators e os formulários verificados são definidos em FSharp.Core.Operators.Checked. Os formulários verificados verificam se há estouro e geram uma exceção de tempo de execução se o valor resultante exceder 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#.

Operator Description
byte Converter em byte, um tipo não assinado de 8 bits.
sbyte Converter em byte assinado.
int16 Converta para um inteiro assinado de 16 bits.
uint16 Converta para um inteiro não assinado de 16 bits.
int32, int Converta para um inteiro assinado de 32 bits.
uint32 Converter em um inteiro não assinado de 32 bits.
int64 Converta para um inteiro assinado de 64 bits.
uint64 Converta para um inteiro não assinado de 64 bits.
nativeint Converta para um número inteiro nativo.
unativeint Converter em um inteiro nativo não assinado.
float, double Converta para um número de ponto flutuante IEEE de precisão dupla de 64 bits.
float32, single Converta para um número de ponto flutuante IEEE de precisão única de 32 bits.
decimal Converter em System.Decimal.
char Converter em System.Char, um 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 trabalha com qualquer tipo que fornece um método op_Explicit estático que toma 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 type que representa o tipo do enum para converter. Quando ele é convertido em um tipo enumerado, a inferência de tipo tenta determinar o tipo do enum que você deseja converter. No exemplo a seguir, a variável col1 não é explicitamente anotada, mas seu tipo é inferido a partir do teste de igualdade posterior. Portanto, o compilador pode deduzir que você está convertendo para uma Color enumeração. Como alternativa, você pode fornecer uma anotação de tipo, como no col2 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 type, como no código a seguir:

let col3 = enum<Color> 3

Observe que as versõ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 não consegue 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 transmissão

A conversão entre tipos em uma hierarquia de objetos é fundamental para a programação orientada a objetos. Existem dois tipos básicos de conversões: casting up (upcasting) e casting down (downcasting). Transmitir uma hierarquia significa transmitir de uma referência de objeto derivado para uma referência de objeto base. Tal elenco é garantido para funcionar desde que a classe base esteja na hierarquia de herança da classe derivada. A derrubada de uma hierarquia, de uma referência de objeto base para uma referência de objeto derivada, 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 lança a hierarquia para cima e o :?> operador lança para baixo a hierarquia.

Upcasting

Em muitas linguagens orientadas a objetos, o upcasting está 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 um cast estático, o que significa que o sucesso do cast é determinado em tempo de compilação. Se um cast que usa :> compila com sucesso, é um cast válido e não tem 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 acima na hierarquia:

upcast expression

Quando você usa o operador upcast, o compilador tenta inferir o tipo para o qual você está convertendo a partir do contexto. Se o compilador não conseguir determinar o tipo de destino, o compilador relata um erro. Pode ser necessária uma anotação de tipo.

Downcasting

O :?> operador executa uma transmissão dinâmica, o que significa que o sucesso da transmissão é determinado em tempo de execução. Um cast que usa o operador não é verificado :?> em tempo de compilação, mas em tempo de execução, uma tentativa é feita para converter para o tipo especificado. Se o objeto for compatível com o tipo de destino, a transmissão será bem-sucedida. Se o objeto não for compatível com o tipo de destino, o tempo de execução gerará um InvalidCastExceptionarquivo .

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 pode inferir um tipo de destino específico a partir do contexto, ele relata um erro. Pode ser necessária uma anotação de tipo.

O código a seguir ilustra o uso dos :> operadores e :?> . O código ilustra que o operador é melhor usado quando você sabe que a :?> conversão será bem-sucedida, porque ele é lançado InvalidCastException se a conversão falhar. Se você não sabe 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 downcast genéricos dependem upcast 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 poderia determinar a classe base.

Conversões upcast implícitas

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

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

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

  • Quando uma ramificação de uma if/then/else ou match expressão tem um tipo de destino conhecido que surge de outra ramificação ou tipo conhecido em geral.

  • Quando um elemento de uma expressão de lista, matriz ou 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 ramos do cálculo condicional a TextReader e StreamReader respectivamente. Na segunda ramificação, o tipo de destino conhecido é TextReader da anotação de tipo no método e da primeira ramificação. Isso significa que nenhum upcast é necessário no segundo ramo.

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

Conversões numéricas implícitas

F# usa alargamento explícito de tipos numéricos na maioria dos casos através de operadores de conversão. Por exemplo, o alargamento explícito é necessário para a maioria dos tipos numéricos, como int8 ou int16, ou de para , ou quando qualquer float64tipo de float32 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 típica de API:

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 acontece automaticamente para int32 , para nativeint, e int32 para double, quando os tipos de origem e destino são conhecidos durante a int64int32 inferência de tipo. Assim, em casos como os exemplos anteriores, int32 literais podem ser usados:

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

Opcionalmente, você também pode ativar o aviso 3389 (/warnon:3389 ou propriedade <WarnOn>3389</WarnOn>) para mostrar um aviso em cada ponto em que o alargamento numérico implícito é usado.

. Conversões implícitas no estilo NET

As APIs do .NET permitem a definição de op_Implicit métodos estáticos para fornecer conversões implícitas entre tipos. Eles são aplicados automaticamente no código F# ao passar argumentos para métodos. Por exemplo, considere o seguinte código fazendo chamadas explícitas para 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 no 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")

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

. As conversões no estilo op_Implicit NET também são aplicadas automaticamente para expressões que não são argumentos de método nas mesmas situações que as versões positivas implícitas. 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 sem argumento.

Para mostrar um aviso em cada ponto 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 (adicional implícito reformulado)
  • /warnon:3389 (alargamento numérico implícito)
  • /warnon:3391op_Implicit( em argumentos não metodológicos, ativados por padrão)
  • /warnon:3395op_Implicit( em argumentos de método)

Consulte também