Conversiones 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 tienen formularios activados y desactivados; los operadores de punto flotante y el enum operador de conversión no. Los formularios desactivados se definen en y los formularios activados FSharp.Core.Operators se definen en FSharp.Core.Operators.Checked . Los formularios activados 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 la segunda 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 Convertir en byte, un tipo sin signo de 8 bits.
sbyte Convertir en byte con signo.
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 Convertir en un entero nativo sin signo.
float, double Convertir en un número de punto flotante IEEE de precisión doble de 64 bits.
float32, single Convertir en un número de punto flotante IEEE de precisión sencilla de 32 bits.
decimal Convertir en System.Decimal .
char Convertir 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 métodos op_Explicit o con las firmas op_Implicit adecuadas. Por ejemplo, el operador de conversión funciona con cualquier tipo que proporciona un método estático que toma el tipo como int op_Explicit parámetro y devuelve int . Como excepción especial a la regla general de que los métodos no se pueden sobrecargar por el tipo de valor devuelto, puede hacerlo para op_Explicit y op_Implicit .

Tipos enumerados

El operador es un operador genérico que toma un parámetro de tipo que representa el tipo de al enum que se va a enum convertir. Cuando se convierte en un tipo enumerado, la inferencia de tipos intenta determinar el tipo de al que enum desea convertir. En el ejemplo siguiente, la variable no se anota explícitamente, pero su tipo se col1 deduce de la prueba de igualdad posterior. Por lo tanto, el compilador puede deducir que se 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 conversión 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 discrepancia entre int32 y uint32 .

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

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

Conversión de tipos de objetos

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: conversión (upcasting) y conversión hacia abajo (downcasting). Convertir una jerarquía significa convertir de una referencia de objeto derivada a una referencia de objeto base. Se garantiza que esta conversión funcionará siempre que la clase base esté en la jerarquía de herencia de la clase derivada. La conversión de una jerarquía, de 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 un tipo derivado del tipo de destino.

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

Upcasting

En muchos lenguajes orientados a objetos, la conversión hacia arriba es implícita; En F#, las reglas son ligeramente diferentes. La upcasting 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 upcasting no es automática, a menos que el tipo de parámetro se declare como un tipo flexible. Para obtener más información, vea 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. Puede ser necesaria una anotación de tipo.

Downcasting

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 operador downcast 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 operador , si el compilador no puede inferir un tipo de destino específico upcast del contexto, notifica un error. Puede ser necesaria 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, porque se produce si se produce un error :?> InvalidCastException en la conversión. Si no sabe que una conversión se realizará correctamente, una prueba de tipo que usa una expresión es mejor porque evita la sobrecarga de generar match 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 y se basan en downcast la inferencia de tipos para determinar el argumento y el tipo de valor devuelto, puede reemplazar en el ejemplo upcast de código anterior por let base1 = d1 :> Base1 let base1: Base1 = upcast d1 .

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

Conversiones de conversión upcast implícitas

Las conversiones upcast 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 las expresiones de cálculo o lalicación se convierte en una llamada de 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 tiene un tipo de destino conocido que se deriva de otra rama o de un tipo match conocido general.

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

Por ejemplo, considere el siguiente 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")

En este caso, las ramas del proceso condicional son TextReader y StreamReader , respectivamente. En la segunda rama, el tipo de destino conocido es de la TextReader 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 punto en el que se usa una conversión implícita adicional, puede habilitar la advertencia 3388 ( /warnon:3388 o la 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 o , o de a , o cuando se desconoce el tipo de origen int8 int16 o de float32 float64 destino.

Sin embargo, se permite el ampliación implícita para enteros de 32 bits que se amplían a enteros de 64 bits, en las mismas situaciones que las conversiones upcast implícitas. Por ejemplo, considere una forma de API típica:

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 a , a y a , cuando se conocen tanto el tipo de origen como el de destino int32 int64 durante la int32 nativeint int32 double 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 ( o propiedad ) para mostrar una advertencia en cada momento en que se usa el ampliación numérico /warnon:3389 <WarnOn>3389</WarnOn> implícito.

. Conversiones implícitas de estilo NET

Las API de .NET permiten que la definición de op_Implicit métodos estáticos proporcione conversiones implícitas entre tipos. Se aplican automáticamente en el código de F# al pasar argumentos a métodos. Por ejemplo, considere el siguiente código 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 NET se aplican automáticamente a las expresiones de argumento cuando los tipos están disponibles para la expresión de origen op_Implicit 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 ( o propiedad ) para mostrar /warnon:3395 una advertencia en cada punto de <WarnOn>3395</WarnOn> . Se usa la conversión implícita de estilo NET.

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

Para mostrar una advertencia en cada punto que un . 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 la propiedad <WarnOn>3391</WarnOn> ).

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

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

Vea también