Compartir a través de


Conversión y conversiones (F#)

En este artículo se describe la compatibilidad con las conversiones de tipos en F#.

Tipos aritméticos

F# proporciona operadores de conversión para conversiones aritméticas entre varios tipos primitivos, como entre tipos enteros y de punto flotante. Los operadores de conversión integral y char han comprobado y desmarcado formularios; los operadores de punto flotante y el enum operador de conversión no lo hacen. Los formularios sin marcar se definen en FSharp.Core.Operators y los formularios comprobados se definen en FSharp.Core.Operators.Checked. Los formularios comprobados comprueban si hay desbordamiento y generan una excepción en tiempo de ejecución si el valor resultante supera los límites del tipo de destino.

Cada uno de estos operadores tiene el mismo nombre que el nombre del tipo de destino. Por ejemplo, en el código siguiente, en el que los tipos se anotan explícitamente, byte aparece con dos significados diferentes. La primera aparición es el tipo y el segundo es el operador de conversión.

let x : int = 5

let b : byte = byte x

En la tabla siguiente se muestran los operadores de conversión definidos en F#.

Operador Descripción
byte Convierta en byte, un tipo sin signo de 8 bits.
sbyte Convierta en byte firmado.
int16 Convierta en un entero de 16 bits con signo.
uint16 Convierta en un entero de 16 bits sin signo.
int32, int Convierta en un entero de 32 bits con signo.
uint32 Convierta en un entero de 32 bits sin signo.
int64 Convierta en un entero de 64 bits con signo.
uint64 Convierta en un entero de 64 bits sin signo.
nativeint Convertir en un entero nativo.
unativeint Convierta en un entero nativo sin signo.
float, double Convierta en un número de punto flotante IEEE de precisión doble de 64 bits.
float32, single Convierta en un número de punto flotante IEEE de precisión única de 32 bits.
decimal Convierta en System.Decimal.
char Convierta en System.Char, un carácter Unicode.
enum Convertir en un tipo enumerado.

Además de los tipos primitivos integrados, puede usar estos operadores con tipos que implementan op_Explicit o op_Implicit métodos con firmas adecuadas. Por ejemplo, el int operador de conversión funciona con cualquier tipo que proporcione un método op_Explicit estático que tome el tipo como parámetro y devuelva int. Como excepción especial a la regla general que los métodos no se pueden sobrecargar por tipo de valor devuelto, puede hacerlo para op_Explicit y op_Implicit.

Tipos enumerados

El enum operador es un operador genérico que toma un parámetro de tipo que representa el tipo de al enum que se va a convertir. Cuando se convierte en un tipo enumerado, la inferencia de tipos intenta determinar el tipo del enum al que desea convertir. En el ejemplo siguiente, la variable col1 no se anota explícitamente, pero su tipo se deduce de la prueba de igualdad posterior. Por lo tanto, el compilador puede deducir que está convirtiendo en una Color enumeración. Como alternativa, puede proporcionar una anotación de tipo, como en col2 el ejemplo siguiente.

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

También puede especificar explícitamente el tipo de enumeración de destino como parámetro de tipo, como en el código siguiente:

let col3 = enum<Color> 3

Tenga en cuenta que las conversiones de enumeración solo funcionan si el tipo subyacente de la enumeración es compatible con el tipo que se va a convertir. En el código siguiente, la conversión no se puede compilar debido a la falta de coincidencia entre int32 y uint32.

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

Para obtener más información, vea Enumeraciones.

Tipos de objeto de conversión

La conversión entre tipos de una jerarquía de objetos es fundamental para la programación orientada a objetos. Hay dos tipos básicos de conversiones: la conversión (difusión) y la conversión (degradación). La conversión de una jerarquía significa convertir desde una referencia de objeto derivada a una referencia de objeto base. Se garantiza que dicha conversión funcione siempre que la clase base esté en la jerarquía de herencia de la clase derivada. La conversión de una jerarquía, desde una referencia de objeto base a una referencia de objeto derivada, solo se realiza correctamente si el objeto es realmente una instancia del tipo de destino correcto (derivado) o de un tipo derivado del tipo de destino.

F# proporciona operadores para estos tipos de conversiones. El :> operador convierte la jerarquía y el :?> operador convierte la jerarquía en la jerarquía.

Conversión a tipo básico

En muchos lenguajes orientados a objetos, la difusión es implícita; en F#, las reglas son ligeramente diferentes. La difusión se aplica automáticamente cuando se pasan argumentos a métodos en un tipo de objeto. Sin embargo, para las funciones enlazadas a let en un módulo, la difusión no es automática, a menos que el tipo de parámetro se declare como un tipo flexible. Para obtener más información, consulte Tipos flexibles.

El :> operador realiza una conversión estática, lo que significa que el éxito de la conversión se determina en tiempo de compilación. Si una conversión que usa :> compila correctamente, es una conversión válida y no tiene ninguna posibilidad de error en tiempo de ejecución.

También puede usar el upcast operador para realizar dicha conversión. La expresión siguiente especifica una conversión en la jerarquía:

upcast expression

Cuando se usa el operador upcast, el compilador intenta deducir el tipo al que se va a convertir desde el contexto. Si el compilador no puede determinar el tipo de destino, el compilador notifica un error. Es posible que se requiera una anotación de tipo.

Conversión a tipo heredado

El :?> operador realiza una conversión dinámica, lo que significa que el éxito de la conversión se determina en tiempo de ejecución. Una conversión que usa el :?> operador no se comprueba en tiempo de compilación; pero en tiempo de ejecución, se intenta convertir al tipo especificado. Si el objeto es compatible con el tipo de destino, la conversión se realiza correctamente. Si el objeto no es compatible con el tipo de destino, el tiempo de ejecución genera un InvalidCastException.

También puede usar el downcast operador para realizar una conversión de tipos dinámicos. La expresión siguiente especifica una conversión de la jerarquía a un tipo que se deduce del contexto del programa:

downcast expression

En cuanto al upcast operador , si el compilador no puede deducir un tipo de destino específico del contexto, notifica un error. Es posible que se requiera una anotación de tipo.

En el código siguiente se muestra el uso de los :> operadores y :?> . El código muestra que el operador se usa mejor cuando se sabe que la :?> conversión se realizará correctamente, ya que se produce InvalidCastException si se produce un error en la conversión. Si no sabe que una conversión se realizará correctamente, una prueba de tipo que usa una match expresión es mejor porque evita la sobrecarga de generar una excepción.

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

Dado que los operadores genéricos downcast y upcast se basan en la inferencia de tipos para determinar el argumento y el tipo de valor devuelto, puede reemplazar let base1 = d1 :> Base1 en el ejemplo de código anterior por let base1: Base1 = upcast d1.

Se requiere una anotación de tipo, porque upcast por sí solo no se pudo determinar la clase base.

Conversiones de difusión implícitas

Las conversiones implícitas se insertan en las situaciones siguientes:

  • Al proporcionar un parámetro a una función o método con un tipo con nombre conocido. Esto incluye cuando una construcción como expresiones de cálculo o segmentación se convierte en una llamada al método.

  • Al asignar o mutar un campo de registro o una propiedad que tiene un tipo con nombre conocido.

  • Cuando una rama de una if/then/else expresión o match tiene un tipo de destino conocido derivado de otra rama o tipo conocido general.

  • Cuando un elemento de una lista, matriz o expresión de secuencia tiene un tipo de destino conocido.

Por ejemplo, considere el código siguiente:

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

Aquí las ramas del proceso condicional a TextReader y StreamReader respectivamente. En la segunda rama, el tipo de destino conocido procede TextReader de la anotación de tipo en el método y de la primera rama. Esto significa que no se necesita ninguna difusión en la segunda rama.

Para mostrar una advertencia en cada momento se usa una difusión implícita adicional, puede habilitar la advertencia 3388 (/warnon:3388 o propiedad <WarnOn>3388</WarnOn>).

Conversiones numéricas implícitas

F# usa la ampliación explícita de tipos numéricos en la mayoría de los casos a través de operadores de conversión. Por ejemplo, se necesita una ampliación explícita para la mayoría de los tipos numéricos, como int8 o int16, o de float32 a float64, o cuando se desconoce el tipo de origen o de destino.

Sin embargo, el ancho implícito se permite para enteros de 32 bits ampliados a enteros de 64 bits, en las mismas situaciones que las conversiones implícitas. Por ejemplo, considere una forma típica de API:

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

Se pueden usar literales enteros para int64:

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

O literales enteros para int32:

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

La ampliación se produce automáticamente para int32int64, int32 a nativeinty int32 a double, cuando se conocen tanto el tipo de origen como el de destino durante la inferencia de tipos. Por lo tanto, en casos como los ejemplos anteriores, int32 se pueden usar literales:

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

También puede habilitar opcionalmente la advertencia 3389 (/warnon:3389 o propiedad <WarnOn>3389</WarnOn>) para mostrar una advertencia en cada punto se usa la ampliación numérica implícita.

. Conversiones implícitas de estilo NET

Las API de .NET permiten la definición de op_Implicit métodos estáticos para proporcionar conversiones implícitas entre tipos. Se aplican automáticamente en código F# al pasar argumentos a métodos. Por ejemplo, considere el código siguiente que realiza llamadas explícitas a op_Implicit métodos:

open System.Xml.Linq

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

. Las conversiones de estilo op_Implicit NET se aplican automáticamente para las expresiones de argumento cuando los tipos están disponibles para la expresión de origen y el tipo de destino:

open System.Xml.Linq

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

También puede habilitar opcionalmente la advertencia 3395 (/warnon:3395 o la propiedad <WarnOn>3395</WarnOn>) para mostrar una advertencia en cada punto. Se usa la conversión implícita de estilo NET.

. Las conversiones de estilo op_Implicit NET también se aplican automáticamente para expresiones que no son de argumento de método en las mismas situaciones que las conversiones implícitas. Sin embargo, cuando se usa ampliamente o inapropiadamente, las conversiones implícitas pueden interactuar mal con la inferencia de tipos y provocar código más difícil de entender. Por este motivo, siempre generan advertencias cuando se usan en posiciones que no son de argumento.

Para mostrar una advertencia en cada punto que es . La conversión implícita de estilo NET se usa para un argumento que no es de método; puede habilitar la advertencia 3391 (/warnon:3391 o propiedad <WarnOn>3391</WarnOn>).

Se proporcionan las siguientes advertencias opcionales para los usos de conversiones implícitas:

  • /warnon:3388 (difusión implícita adicional)
  • /warnon:3389 (ampliación numérica implícita)
  • /warnon:3391 (op_Implicit en argumentos no métodos, activado de forma predeterminada)
  • /warnon:3395 (op_Implicit en argumentos de método)

Consulte también