Compartilhar 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 dá suporte à programação orientada a objetos em F#.

Na sintaxe anterior, é qualquer type-name identificador válido. Descreve os type-params parâmetros de tipo genérico opcionais. Ele consiste em nomes de parâmetro de tipo e restrições entre colchetes angulares (< e >). Para obter mais informações, consulte Genéricos e Restrições. Descreve os parameter-list 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.

Especifique a classe base para uma classe usando a inherit palavra-chave. Você deve fornecer argumentos, entre parênteses, para o construtor de 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 o código a ser executado na construção do objeto.

Consiste member-list em construtores adicionais, declarações de método estático e de instância, declarações de interface, associações abstratas e declarações de propriedade e evento. Elas são descritas em Membros.

O identifier que é usado com a palavra-chave opcional as fornece um nome para a variável de instância ou auto-identificador, 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 Auto-identificadores mais adiante neste tópico.

As palavras-chave class e end que marcam o início e o final 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 and palavra-chave da mesma forma que as funções mutuamente recursivas são. Para obter um exemplo, consulte a seção Tipos Mutuamente Recursivos.

Construtores

O construtor é um 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 tipo e cujo corpo consiste nas let associações (e let rec) no início da declaração de classe e das do associações a seguir. 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 aceita argumentos.

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

let and do Bindings

As let associações e do as associações 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 associação for uma função, ela será compilada em um membro. Se a let associação for um valor que não é usado em nenhuma função ou membro, ela será compilada em uma variável local para o construtor. Caso contrário, ele será 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 todos os construtores adicionais sempre chamam o construtor primário, as associações e do as let associações sempre são executadas independentemente de qual construtor é chamado.

Os campos 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 de métodos estáticos, mesmo que os métodos estáticos usem uma variável de instância como parâmetro. Eles não poderão ser acessados usando o auto-identificador, se houver.

Auto-identificadores

Um auto-identificador é um nome que representa a instância atual. Os auto-identificadores se assemelham à this palavra-chave em C# ou C++ ou Me no Visual Basic. Você pode definir um auto-identificador de duas maneiras diferentes, dependendo se deseja que o auto-identificador esteja no escopo de toda a definição de classe ou apenas para um método individual.

Para definir um auto-identificador 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 auto-identificador para apenas um método, forneça o auto-identificador na declaração de membro, pouco 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 auto-identificador. Na primeira linha, a as palavra-chave é usada para definir o auto-identificador. Na quinta linha, o identificador this é usado para definir um auto-identificador 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 outros idiomas do .NET, você pode nomear o auto-identificador como desejar; você não está restrito a nomes como self, Meou this.

O auto-identificador 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 runtime. Você pode usar o auto-identificador livremente após o construtor base, como em let associações ou do associações.

Parâmetros de tipo genérico

Parâmetros de tipo genérico são especificados em colchetes angulares (< e >), na forma de uma única aspa seguida por um identificador. Vários parâmetros de tipo genérico são separados por vírgulas. O parâmetro de tipo genérico está no escopo em 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

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 uma. 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 de idioma como um identificador, seguido por um período (.) e o nome do membro.

Para obter mais informações, consulte Herança.

Seção 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 associações let and do não podem aparecer nesta seção. Como os membros podem ser adicionados a uma variedade de tipos F# além de classes, eles são discutidos em um tópico separado, Membros.

Tipos mutuamente recursivos

Quando você define tipos que fazem referência entre si 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 maneira.

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

Considerando a variedade de tipos a serem escolhidos, você precisa ter uma boa compreensão do que cada tipo foi projetado para selecionar o tipo apropriado para uma situação específica. As classes são projetadas para uso em contextos de programação orientados a objetos. A programação orientada a objetos é o paradigma dominante usado em aplicativos que são escritos para o .NET Framework. Se o código F# precisar trabalhar em estreita colaboração com o .NET Framework ou outra biblioteca orientada a objetos e, especialmente, se você precisar estender de um sistema de tipos orientado a objetos, como uma biblioteca de interface do usuário, as classes provavelmente serão apropriadas.

Se você não estiver interoperando de perto com código orientado a objeto ou se estiver escrevendo um código que seja autocontido e, portanto, protegido contra interação frequente com código orientado a objeto, considere usar uma combinação de classes, registros e uniões discriminadas. Uma união discriminada única e bem pensada, juntamente com o código de correspondência de padrões apropriado, geralmente pode ser usada como uma alternativa mais simples para uma hierarquia de objetos. Para obter mais informações sobre sindicatos discriminados, consulte Uniões Discriminadas.

Os registros têm a vantagem de serem mais simples do que as classes, mas os registros não são apropriados quando as demandas de um tipo excedem o que pode ser feito com sua simplicidade. Os registros são basicamente agregações simples 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 agregação simples 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, pois são tipos de valor .NET. Classes e registros são tipos de referência do .NET. A semântica de tipos de valor e tipos de referência são diferentes, pois os tipos de valor são passados por valor. Isso significa que eles são copiados bit para 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, inseridos dentro do objeto pai em vez de armazenados em seu próprio local separado no heap. 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