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 int
funciona 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_Explicit
e 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
oumatch
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>
).
Resumo de avisos relacionados a conversões
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)