Partilhar via


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.

Consulte também