Extensiones de tipo

Las extensiones de tipo (también denominadas aumentos) son una familia de características que permiten agregar nuevos miembros a un tipo de objeto definido previamente. Las tres características son:

  • Extensiones de tipo intrínseco
  • Extensiones de tipo opcionales
  • Métodos de extensión

Cada uno se puede usar en diferentes escenarios y tiene diferentes ventajas.

Sintaxis

// Intrinsic and optional extensions
type typename with
    member self-identifier.member-name =
        body
    ...

// Extension methods
open System.Runtime.CompilerServices

[<Extension>]
type Extensions() =
    [<Extension>]
    static member extension-name (ty: typename, [args]) =
        body
    ...

Extensiones de tipo intrínseco

Una extensión de tipo intrínseco es una extensión de tipo que extiende un tipo definido por el usuario.

Las extensiones de tipo intrínseco deben definirse en el mismo archivo y en el mismo espacio de nombres o módulo que el tipo que extienden. Cualquier otra definición hará que sean extensiones de tipo opcionales.

Las extensiones de tipo intrínsecas a veces son una manera más limpia de separar la funcionalidad de la declaración de tipo. En el ejemplo siguiente se muestra cómo definir una extensión de tipo intrínseco:

namespace Example

type Variant =
    | Num of int
    | Str of string
  
module Variant =
    let print v =
        match v with
        | Num n -> printf "Num %d" n
        | Str s -> printf "Str %s" s

// Add a member to Variant as an extension
type Variant with
    member x.Print() = Variant.print x

El uso de una extensión de tipo permite separar cada una de las siguientes opciones:

  • Declaración de un tipo Variant
  • Funcionalidad para imprimir la clase Variant en función de su "forma"
  • Una manera de acceder a la funcionalidad de impresión con notación . de estilo de objeto

Se trata de una alternativa a definir todo como miembro en Variant. Aunque no es un enfoque intrínsecamente mejor, puede ser una representación más limpia de la funcionalidad en algunas situaciones.

Las extensiones de tipo intrínseco se compilan como miembros del tipo que aumentan y aparecen en el tipo cuando el tipo se examina mediante reflexión.

Extensiones de tipo opcionales

Una extensión de tipo opcional es una extensión que aparece fuera del módulo original, el espacio de nombres o el ensamblado del tipo que se va a extender.

Las extensiones de tipo opcionales son útiles para extender un tipo que no haya definido usted mismo. Por ejemplo:

module Extensions

type IEnumerable<'T> with
    /// Repeat each element of the sequence n times
    member xs.RepeatElements(n: int) =
        seq {
            for x in xs do
                for _ in 1 .. n -> x
        }

Ahora puede acceder RepeatElements como si fuera miembro de IEnumerable<T> siempre que el módulo Extensions se abra en el ámbito en el que está trabajando.

Las extensiones opcionales no aparecen en el tipo extendido cuando se examina por reflexión. Las extensiones opcionales deben estar en módulos y solo están en el ámbito cuando el módulo que contiene la extensión está abierto o está en el ámbito.

Los miembros de extensión opcionales se compilan en miembros estáticos para los que la instancia de objeto se pasa implícitamente como primer parámetro. Sin embargo, actúan como si fueran miembros de instancia o miembros estáticos según cómo se declaran.

Los miembros de extensión opcionales tampoco son visibles para los consumidores de C# o Visual Basic. Solo se pueden consumir en otro código de F#.

Limitación genérica de extensiones de tipo intrínsecas y opcionales

Es posible declarar una extensión de tipo en un tipo genérico en el que la variable de tipo está restringida. El requisito es que la restricción de la declaración de extensión coincida con la restricción del tipo declarado.

Sin embargo, incluso cuando las restricciones coinciden entre un tipo declarado y una extensión de tipo, es posible que el cuerpo de un miembro extendido infiera una restricción que impone un requisito diferente en el parámetro de tipo que el tipo declarado. Por ejemplo:

open System.Collections.Generic

// NOT POSSIBLE AND FAILS TO COMPILE!
//
// The member 'Sum' has a different requirement on 'T than the type IEnumerable<'T>
type IEnumerable<'T> with
    member this.Sum() = Seq.sum this

No hay ninguna manera de que este código funcione con una extensión de tipo opcional:

  • Como es, el miembro Sum tiene una restricción diferente en 'T (static member get_Zero y static member (+)) que la extensión de tipo define.
  • Modificar la extensión de tipo para que tenga la misma restricción que Sum ya no coincidirá con la restricción definida en IEnumerable<'T>.
  • Al cambiar member this.Sum a member inline this.Sum se producirá un error que indica que las restricciones de tipo no coinciden.

Lo que se desea son métodos estáticos que "flotan en el espacio" y se pueden presentar como si extendan un tipo. Aquí es donde los métodos de extensión son necesarios.

Métodos de extensión

Por último, los métodos de extensión (a veces denominados "miembros de extensión de estilo de C#") se pueden declarar en F# como método de miembro estático en una clase.

Los métodos de extensión son útiles para cuando se desean definir extensiones en un tipo genérico que restringirá la variable de tipo. Por ejemplo:

namespace Extensions

open System.Collections.Generic
open System.Runtime.CompilerServices

[<Extension>]
type IEnumerableExtensions =
    [<Extension>]
    static member inline Sum(xs: IEnumerable<'T>) = Seq.sum xs

Cuando se usa, este código hará que aparezca como si Sum se define en IEnumerable<T>, siempre y cuando Extensions se haya abierto o esté en el ámbito.

Para que la extensión esté disponible para VB.NET código, se requiere un ExtensionAttribute adicional en el nivel de ensamblado:

module AssemblyInfo
open System.Runtime.CompilerServices
[<assembly:Extension>]
do ()

Otros comentarios

Las extensiones de tipo también tienen los siguientes atributos:

  • Se puede ampliar cualquier tipo al que se pueda tener acceso.
  • Las extensiones de tipo intrínsecas y opcionales pueden definir cualquier tipo de miembro, no solo métodos. Por lo tanto, las propiedades de extensión también son posibles, por ejemplo.
  • El token self-identifier de la sintaxis representa la instancia del tipo que se invoca, al igual que los miembros normales.
  • Los miembros extendidos pueden ser miembros estáticos o de instancia.
  • Las variables de tipo de una extensión de tipo deben coincidir con las restricciones del tipo declarado.

También existen las siguientes limitaciones para las extensiones de tipo:

  • Las extensiones de tipo no admiten métodos virtuales o abstractos.
  • Las extensiones de tipo no admiten métodos de invalidación como aumentos.
  • Las extensiones de tipo no admiten parámetros de tipo resueltos estáticamente.
  • Las extensiones de tipo opcionales no admiten constructores como aumentos.
  • Las extensiones de tipo no se pueden definir en abreviaturas de tipo.
  • Las extensiones de tipo no son válidas para byref<'T> (aunque se pueden declarar).
  • Las extensiones de tipo no son válidas para los atributos (aunque se pueden declarar).
  • Puede definir extensiones que sobrecargan otros métodos del mismo nombre, pero el compilador de F# da preferencia a los métodos que no son de extensión si hay una llamada ambigua.

Por último, si existen varias extensiones de tipo intrínseco para un tipo, todos los miembros deben ser únicos. En el caso de las extensiones de tipo opcionales, los miembros de extensiones de tipo diferentes al mismo tipo pueden tener los mismos nombres. Los errores de ambigüedad solo se producen si el código de cliente abre dos ámbitos diferentes que definen los mismos nombres de miembro.

Vea también