Classes (F#)
Classes são tipos que representam objetos que podem ter propriedades, métodos e eventos.
Sintaxe
// 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 ...
...
Observações
As classes representam a descrição fundamental dos tipos de objeto .NET; a classe é o conceito de tipo primário que suporta programação orientada a objeto em F#.
Na sintaxe anterior, o type-name
é qualquer identificador válido. O type-params
descreve parâmetros de tipo genéricos opcionais. Consiste em nomes de parâmetros de tipo e restrições entre colchetes angulares (<
e >
). Para obter mais informações, consulte Genéricos e restrições. O parameter-list
descreve os parâmetros do construtor. O primeiro modificador de acesso pertence ao tipo; o segundo pertence ao construtor primário. Em ambos os casos, o padrão é public
.
Você especifica a classe base para uma classe usando a palavra-chave inherit
. Você deve fornecer argumentos, entre parênteses, para o construtor da classe base.
Você declara campos ou valores de função que são locais para a classe usando let
associações e deve seguir as regras gerais para let
associações. A do-bindings
seção inclui código a ser executado na construção do objeto.
O member-list
consiste em construtores adicionais, declarações de instância e método estático, declarações de interface, associações abstratas e declarações de propriedade e evento. Estes são descritos nos deputados.
O identifier
que é usado com a palavra-chave opcional as
dá um nome para a variável de instância, ou autoidentificador, que pode ser usado na definição de tipo para se referir à instância do tipo. Para obter mais informações, consulte a seção Autoidentificadores mais adiante neste tópico.
As palavras-chave class
e end
que marcam o início e o fim da definição são opcionais.
Tipos mutuamente recursivos, que são tipos que fazem referência uns aos outros, são unidos com a palavra-chave assim como as and
funções mutuamente recursivas. Para obter um exemplo, consulte a seção Tipos mutuamente recursivos.
Construtores
O construtor é o código que cria uma instância do tipo de classe. Construtores para classes funcionam de forma um pouco diferente em F# do que em outras linguagens .NET. Em uma classe F#, há sempre um construtor primário cujos argumentos são descritos no parameter-list
que segue o nome do let
tipo, e cujo corpo consiste nas (e let rec
) ligações no início da declaração de classe e as do
ligações que se seguem. Os argumentos do construtor primário estão no escopo em toda a declaração de classe.
Você pode adicionar construtores adicionais usando a new
palavra-chave para adicionar um membro, da seguinte maneira:
new
(argument-list
) = constructor-body
O corpo do novo construtor deve invocar o construtor primário especificado na parte superior da declaração de classe.
O exemplo a seguir ilustra esse conceito. No código a seguir, MyClass
tem dois construtores, um construtor primário que usa dois argumentos e outro construtor que não usa argumentos.
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
deixar e fazer ligações
As let
ligações e do
em uma definição de classe formam o corpo do construtor de classe primária e, portanto, são executadas sempre que uma instância de classe é criada. Se uma let
ligação é uma função, então ela é compilada em um membro. Se a let
associação é um valor que não é usado em qualquer função ou membro, então ele é compilado em uma variável que é local para o construtor. Caso contrário, ele é compilado em um campo da classe. As do
expressões a seguir são compiladas no construtor primário e executam o código de inicialização para cada instância. Como qualquer construtor adicional sempre chama o construtor primário, as let
ligações e do
ligações sempre são executadas, independentemente de qual construtor é chamado.
Os campos que são criados por let
associações podem ser acessados em todos os métodos e propriedades da classe, no entanto, eles não podem ser acessados a partir de métodos estáticos, mesmo que os métodos estáticos tomem uma variável de instância como parâmetro. Eles não podem ser acessados usando o autoidentificador, se existir.
Autoidentificadores
Um autoidentificador é um nome que representa a instância atual. Os identificadores pessoais se assemelham à this
palavra-chave em C# ou C++ ou Me
no Visual Basic. Você pode definir um autoidentificador de duas maneiras diferentes, dependendo se deseja que o autoidentificador esteja no escopo para toda a definição de classe ou apenas para um método individual.
Para definir um identificador próprio para toda a classe, use a as
palavra-chave após os parênteses de fechamento da lista de parâmetros do construtor e especifique o nome do identificador.
Para definir um identificador de si mesmo para apenas um método, forneça o identificador de si mesmo na declaração de membro, imediatamente antes do nome do método e de um ponto (.) como separador.
O exemplo de código a seguir ilustra as duas maneiras de criar um identificador próprio. Na primeira linha, a as
palavra-chave é usada para definir o autoidentificador. Na quinta linha, o identificador this
é usado para definir um autoidentificador cujo escopo é restrito ao método PrintMessage
.
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
Ao contrário de outras linguagens .NET, você pode nomear o identificador automático como quiser; Você não está restrito a nomes como self
, Me
, ou this
.
O identificador self que é declarado com a as
palavra-chave não é inicializado até depois do construtor base. Portanto, quando usado antes ou dentro do construtor base, System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
será gerado durante o tempo de execução. Você pode usar o identificador self livremente após o construtor base, como em let
bindings ou do
bindings.
Parâmetros de tipo genéricos
Os parâmetros de tipo genéricos são especificados entre parênteses angulares (<
e >
), sob a forma de aspas simples seguidas de um identificador. Vários parâmetros de tipo genéricos são separados por vírgulas. O parâmetro de tipo genérico está no escopo de toda a declaração. O exemplo de código a seguir mostra como especificar parâmetros de tipo genérico.
type MyGenericClass<'a>(x: 'a) =
do printfn "%A" x
Os argumentos de tipo são inferidos quando o tipo é usado. No código a seguir, o tipo inferido é uma sequência de tuplas.
let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })
Especificando herança
A inherit
cláusula identifica a classe base direta, se houver. Em F#, apenas uma classe base direta é permitida. As interfaces que uma classe implementa não são consideradas classes base. As interfaces são discutidas no tópico Interfaces .
Você pode acessar os métodos e as propriedades da classe base da classe derivada usando a palavra-chave base
language como um identificador, seguida por um ponto (.) e o nome do membro.
Para obter mais informações, consulte Herança.
Secção de Membros
Você pode definir métodos estáticos ou de instância, propriedades, implementações de interface, membros abstratos, declarações de evento e construtores adicionais nesta seção. As vinculações Let and Do não podem aparecer nesta seção. Como os membros podem ser adicionados a uma variedade de tipos de F#, além das classes, eles são discutidos em um tópico separado, Membros.
Tipos mutuamente recursivos
Ao definir tipos que fazem referência uns aos outros de forma circular, você agrupa as definições de tipo usando a and
palavra-chave. A and
palavra-chave substitui a type
palavra-chave em todos, exceto na primeira definição, da seguinte forma.
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
A saída é uma lista de todos os arquivos no diretório atual.
Quando usar classes, uniões, registros e estruturas
Dada a variedade de tipos para escolher, você precisa ter uma boa compreensão do que cada tipo é projetado para selecionar o tipo apropriado para uma situação específica. As classes são projetadas para uso em contextos de programação orientada a objetos. A programação orientada a objetos é o paradigma dominante usado em aplicativos que são escritos para o .NET Framework. Se seu código F# tiver que trabalhar em estreita colaboração com o .NET Framework ou outra biblioteca orientada a objeto, e especialmente se você tiver que estender de um sistema de tipo orientado a objeto, como uma biblioteca de interface do usuário, as classes provavelmente serão apropriadas.
Se você não estiver interoperando estreitamente com código orientado a objeto, ou se estiver escrevendo código que é autônomo e, portanto, protegido de interação frequente com código orientado a objeto, você deve considerar o uso de uma combinação de classes, registros e uniões discriminadas. Uma união discriminada única e bem pensada, juntamente com um código de correspondência de padrões apropriado, pode muitas vezes ser usada como uma alternativa mais simples a uma hierarquia de objetos. Para obter mais informações sobre sindicatos discriminados, consulte Sindicatos discriminados.
Os registos têm a vantagem de serem mais simples do que as aulas, mas os registos não são adequados quando as exigências de um tipo excedem o que pode ser realizado com a sua simplicidade. Os registros são basicamente simples agregados de valores, sem construtores separados que podem executar ações personalizadas, sem campos ocultos e sem implementações de herança ou interface. Embora membros como propriedades e métodos possam ser adicionados aos registros para tornar seu comportamento mais complexo, os campos armazenados em um registro ainda são uma simples agregação de valores. Para obter mais informações sobre registros, consulte Registros.
As estruturas também são úteis para pequenas agregações de dados, mas diferem de classes e registros por serem tipos de valor .NET. Classes e registros são tipos de referência .NET. A semântica dos tipos de valor e dos tipos de referência é diferente na medida em que os tipos de valor são passados por valor. Isso significa que eles são copiados bit por bit quando são passados como um parâmetro ou retornados de uma função. Eles também são armazenados na pilha ou, se forem usados como um campo, incorporados dentro do objeto pai em vez de armazenados em seu próprio local separado na pilha. Portanto, as estruturas são apropriadas para dados acessados com frequência quando a sobrecarga de acessar o heap é um problema. Para obter mais informações sobre estruturas, consulte Structs.