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 ...
...

Observaciones

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

En la sintaxis anterior, es type-name cualquier identificador válido. describe type-params los 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. describe parameter-list los 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 para una clase mediante la palabra inherit clave . Debe proporcionar argumentos, entre paréntesis, para el constructor de clase base.

Puede declarar campos o valores de función locales para la clase mediante enlaces y debe seguir las reglas let generales para let los enlaces. En do-bindings la sección se incluye el código que se ejecutará tras la construcción de objetos.

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

El que se usa con la palabra clave opcional proporciona un nombre a la variable de instancia, o identificador propio, que se puede usar en la definición de tipo para hacer referencia a la instancia identifier as del tipo. Para obtener más información, vea la sección Self Identifiers 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 hacen referencia entre sí, se unen con la palabra clave igual que las funciones and 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 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 que sigue al nombre de tipo y cuyo cuerpo consta de los enlaces (y ) al principio de la declaración de clase y los enlaces parameter-list let let rec do siguientes. Los argumentos del constructor principal están en el ámbito a lo largo de la declaración de clase.

Puede agregar constructores adicionales mediante la palabra new clave para agregar un miembro, como se muestra a continuación:

new(argument-list) = constructor-body

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

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

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

let and do Bindings

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

Se puede acceder a los campos creados por enlaces a través de los métodos y propiedades de la clase ; sin embargo, no se puede acceder a ellos desde métodos estáticos, incluso si los métodos estáticos toman una variable de instancia como let parámetro. No se puede acceder a ellos mediante el identificador propio, si existe uno.

Identificadores propios

Un identificador propio es un nombre que representa la instancia actual. Los identificadores propios son similares this a la palabra clave en C# o C++ o en Me Visual Basic. Puede definir un identificador propio de dos maneras diferentes, en función de si desea que el identificador propio esté en el ámbito de toda la definición de clase o solo para un método individual.

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

Para definir un identificador propio para un solo método, proporcione el identificador propio 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 identificador propio. En la primera línea, la as palabra clave se usa para definir el identificador propio. En la quinta línea, el identificador se usa para definir un identificador propio this 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 .NET, puede asignar el nombre que desee al identificador propio. no está restringido a nombres como self , Me o this .

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

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. Varios parámetros de tipo genérico están separados por comas. El parámetro de tipo genérico está en el ámbito a lo largo de 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) } )

Especificar herencia

La inherit cláusula 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 de abordan en el tema Interfaces .

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

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

Sección Miembros

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

Tipos mutuamente recursivos

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

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é está diseñado cada tipo para 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 que se escriben para el .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, las clases probablemente sean adecuadas.

Si no está interoperando estrechamente con código orientado a objetos, o si está escribiendo código independiente y, por tanto, protegido contra la interacción frecuente con código orientado a objetos, debe considerar el uso de una combinación de clases, registros y uniones discriminadas. Una unión discriminada única bien planificada, junto con el código de coincidencia de patrones adecuado, a menudo se puede usar como una alternativa más sencilla a una jerarquía de objetos. Para obtener más información sobre las uniones discriminadas, vea Discriminated Unions.

Los registros tienen la ventaja de ser más sencillos que las clases, pero los registros 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 pueden 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 valor de .NET. Las clases y los registros son tipos de referencia de .NET. La semántica de los tipos de valor y los tipos de referencia es diferente en que los tipos de valor se pasan por valor. Esto significa que se copian bit para bit cuando se pasan como un parámetro o se devuelven desde 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 acceso al montón es un problema. Para obtener más información sobre las estructuras, vea Structs.

Vea también