Clases (F#)

Las clases son tipos que representan objetos que pueden tener propiedades, métodos y eventos.

Sintaxis

// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...

Comentarios

Las clases representan la descripción fundamental de los tipos de objetos de .NET; la clase es el concepto de tipo principal que admite la programación orientada a objetos en F#.

En la sintaxis anterior, type-name es cualquier identificador válido. type-params describe parámetros de tipo genérico opcionales. Consta de nombres de parámetros de tipo y restricciones entre corchetes angulares (< y >). Para obtener más información, vea Genéricos y Restricciones. parameter-list describe parámetros del constructor. El primer modificador de acceso pertenece al tipo; el segundo pertenece al constructor principal. En ambos casos, el valor predeterminado es public.

Especifique la clase base de una clase mediante la palabra clave inherit. Debe proporcionar argumentos, entre paréntesis, para el constructor de la clase base.

Puede declarar campos o valores de función que son locales respecto a la clase mediante enlaces let, y debe seguir las reglas generales de los enlaces let. La sección do-bindings incluye código que se va a ejecutar tras la construcción del objeto.

member-list consta de constructores adicionales, declaraciones de método estático y de instancia, declaraciones de interfaz, enlaces abstractos y declaraciones de propiedades y eventos. Estos se describen en Miembros.

El identifier que se usa con la palabra clave opcional as proporciona un nombre a la variable de instancia, o autoidentificador, que se puede usar en la definición de tipo para referirse a la instancia del tipo. Para obtener más información, vea la sección Autoidentificadores más adelante en este tema.

Las palabras clave class y end que marcan el inicio y el final de la definición son opcionales.

Los tipos mutuamente recursivos, que son tipos que se refieren unos a otros, se unen con la palabra clave and igual que las funciones mutuamente recursivas. Para obtener un ejemplo, vea la sección Tipos mutuamente recursivos.

Constructores

El constructor es código que crea una instancia del tipo de clase. Los constructores de las clases funcionan de forma ligeramente diferente en F# que en otros lenguajes .NET. En una clase de F# siempre hay un constructor principal cuyos argumentos se describen en el valor parameter-list que sigue al nombre de tipo, y cuyo cuerpo consta de los enlaces let (y let rec) al principio de la declaración de clase y los enlaces do que siguen. Los argumentos del constructor principal están en el ámbito durante la declaración de clase.

Puede agregar constructores adicionales mediante la palabra clave new para agregar un miembro, como sigue:

new(argument-list) = constructor-body

El cuerpo del nuevo constructor debe invocar al constructor principal que se ha especificado en la parte superior de la declaración de clase.

En el siguiente ejemplo se ilustra este concepto. En el código siguiente, MyClass tiene dos constructores, un constructor principal que toma dos argumentos y otro que no toma ningún argumento.

type MyClass1(x: int, y: int) =
    do printfn "%d %d" x y
    new() = MyClass1(0, 0)

Enlaces let y do

Los enlaces let y do de una definición de clase forman el cuerpo del constructor de clase principal y, por tanto, se ejecutan cada vez que se crea una instancia de clase. Si un enlace let es una función, se compila en un miembro. Si el enlace let es un valor que no se usa en ninguna función o miembro, se compila en una variable que es local con respecto al constructor. De lo contrario, se compila en un campo de la clase. Las expresiones do que siguen se compilan en el constructor principal y ejecutan código de inicialización de cada instancia. Dado que cualquier constructor adicional siempre llama al constructor principal, los enlaces let y do siempre se ejecutan independientemente del constructor al que se llame.

Se puede acceder a los campos creados mediante enlaces let por medio de los métodos y las propiedades de la clase; pero no se puede acceder a ellos desde métodos estáticos, incluso si los métodos estáticos toman una variable de instancia como parámetro. No se puede acceder a ellos mediante el autoidentificador, si existe alguno.

Autoidentificadores

Un autoidentificador es un nombre que representa a la instancia actual. Los autoidentificadores son similares a la palabra clave thisde C# o C++ o Me de Visual Basic. Puede definir un autoidentificador de dos maneras diferentes, en función de si quiere que el autoidentificador esté en el ámbito de la definición de clase completa o solo en un método individual.

Para definir un autoidentificador para toda la clase, use la palabra clave as después de los paréntesis de cierre de la lista de parámetros del constructor y especifique el nombre del identificador.

Para definir un autoidentificador para un solo método, proporcione el autoidentificador en la declaración de miembro, justo antes del nombre del método y un punto (.) como separador.

En el ejemplo de código siguiente se muestran las dos maneras de crear un autoidentificador. En la primera línea se usa la palabra clave as para definir el autoidentificador. En la quinta línea se usa el identificador this para definir un autoidentificador cuyo ámbito está restringido al método PrintMessage.

type MyClass2(dataIn) as self =
    let data = dataIn
    do
        self.PrintMessage()
    member this.PrintMessage() =
        printf "Creating MyClass2 with Data %d" data

A diferencia de otros lenguajes de .NET, puede asignar un nombre al autoidentificador de la forma que quiera; no está restringido a nombres como self, Me o this.

El autoidentificador que se declara con la palabra clave as no se inicializa hasta después del constructor base. Por lo tanto, si se usa antes o dentro del constructor base, se genera System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. durante el tiempo de ejecución. Puede usar el autoidentificador libremente después del constructor base, como en enlaces let o do.

Parámetros de tipos genéricos

Los parámetros de tipo genérico se especifican entre corchetes angulares (< y >), en forma de comillas simples seguidas de un identificador. Si hay varios parámetros de tipo genérico, se separan mediante comas. El parámetro de tipo genérico está en el ámbito durante toda la declaración. En el ejemplo de código siguiente se muestra cómo especificar parámetros de tipo genérico.

type MyGenericClass<'a>(x: 'a) =
    do printfn "%A" x

Los argumentos de tipo se deducen cuando se usa el tipo. En el código siguiente, el tipo deducido es una secuencia de tuplas.

let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })

Especificación de herencia

La cláusula inherit identifica la clase base directa, si hay una. En F#, solo se permite una clase base directa. Las interfaces que implementa una clase no se consideran clases base. Las interfaces se tratan en el tema Interfaces.

Puede acceder a los métodos y las propiedades de la clase base desde la clase derivada mediante la palabra clave del lenguaje base como identificador, seguida de un punto (.) y el nombre del miembro.

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

Sección de miembros

En esta sección puede definir métodos estáticos o de instancia, propiedades, implementaciones de interfaz, miembros abstractos, declaraciones de eventos y constructores adicionales. Los enlaces let y do no pueden aparecer en esta sección. Dado que los miembros se pueden agregar a una serie de tipos de F# además de a clases, se tratan en un tema independiente, Miembros.

Tipos mutuamente recursivos

Al definir tipos que se hacen referencia entre sí de forma circular, se agrupan las definiciones de tipo mediante la palabra clave and. La palabra clave and reemplaza a la palabra clave type en todas, excepto la primera definición, como sigue.

open System.IO

type Folder(pathIn: string) =
    let path = pathIn
    let filenameArray: string array = Directory.GetFiles(path)
    member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray

and File(filename: string, containingFolder: Folder) =
    member this.Name = filename
    member this.ContainingFolder = containingFolder

let folder1 = new Folder(".")

for file in folder1.FileArray do
    printfn "%s" file.Name

La salida es una lista de todos los archivos del directorio actual.

Cuándo usar clases, uniones, registros y estructuras

Dada la variedad de tipos entre los que elegir, debe tener una buena comprensión de para qué se ha diseñado cada tipo a fin de seleccionar el tipo adecuado para una situación determinada. Las clases están diseñadas para su uso en contextos de programación orientados a objetos. La programación orientada a objetos es el paradigma dominante que se usa en las aplicaciones escritas para .NET Framework. Si el código de F# tiene que trabajar estrechamente con .NET Framework u otra biblioteca orientada a objetos, y especialmente si tiene que extender desde un sistema de tipos orientado a objetos, como una biblioteca de interfaz de usuario, es probable que las clases sean adecuadas.

Si no está interoperando estrechamente con código orientado a objetos, o si está escribiendo código autocontenido y, por lo tanto, protegido de la interacción frecuente con código orientado a objetos, debe considerar la posibilidad de usar una combinación de clases, registros y uniones discriminadas. Una unión discriminada única y bien pensada, junto con código de coincidencia de patrones adecuado, se suele poder usar como una alternativa más sencilla a una jerarquía de objetos. Para obtener información sobre las uniones discriminadas, vea Uniones discriminadas.

Los registros tienen la ventaja de ser más sencillos que las clases, pero no son adecuados cuando las demandas de un tipo superan lo que se puede lograr con su simplicidad. Los registros son básicamente agregados simples de valores, sin constructores independientes que puedan realizar acciones personalizadas, sin campos ocultos y sin implementaciones de herencia o interfaz. Aunque se pueden agregar miembros como propiedades y métodos a los registros para que su comportamiento sea más complejo, los campos almacenados en un registro siguen siendo un agregado simple de valores. Para obtener más información sobre los registros, vea Registros.

Las estructuras también son útiles para pequeños agregados de datos, pero difieren de las clases y los registros en que son tipos de valores de .NET. Las clases y los registros son tipos de referencias de .NET. La semántica de los tipos de valores y los tipos de referencias se diferencia en que los tipos de valores se pasan por valor. Esto significa que se copian bit a bit cuando se pasan como un parámetro o se devuelven de una función. También se almacenan en la pila o, si se usan como campo, se insertan dentro del objeto primario en lugar de almacenarse en su propia ubicación independiente en el montón. Por lo tanto, las estructuras son adecuadas para los datos a los que se accede con frecuencia cuando la sobrecarga de acceder al montón es un problema. Para obtener más información sobre las estructuras, vea Estructuras.

Vea también