Assinaturas

Um arquivo de assinatura contém informações sobre as assinaturas públicas de um conjunto de elementos de programação de F#, como tipos, namespaces e módulos. Ele pode ser usado para especificar a acessibilidade desses elementos de programa.

Comentários

Para cada arquivo de código de F#, é possível ter um arquivo de assinatura, que tem o mesmo nome do arquivo de código, mas a extensão .fsi em vez de .fs. Arquivos de assinatura também podem ser adicionados à linha de comando de compilação ao usar diretamente a linha de comando. Para distinguir entre arquivos de código e arquivos de assinatura, os arquivos de código são chamados algumas vezes de arquivos de implementação. Em um projeto, o arquivo de assinatura deve preceder o arquivo de código associado.

Um arquivo de assinatura descreve os namespaces, módulos, tipos e membros no arquivo de implementação correspondente. As informações em um arquivo de assinatura são usadas para especificar quais partes do código no arquivo de implementação correspondente podem ser acessadas por meio do código fora do arquivo de implementação e quais partes são internas ao arquivo de implementação. Os namespaces, módulos e tipos incluídos no arquivo de assinatura devem ser um subconjunto dos namespaces, módulos e tipos incluídos no arquivo de implementação. Com algumas exceções que são observadas posteriormente neste tópico, os elementos de linguagem que não estão listados no arquivo de assinatura são considerados privados e do arquivo de implementação. Se nenhum arquivo de assinatura for encontrado no projeto ou na linha de comando, a acessibilidade padrão será usada.

Para saber mais sobre a acessibilidade padrão, confira Controle de acesso.

Em um arquivo de assinatura, a definição dos tipos e as implementações de cada método ou função não são repetidas. Em vez disso, você usa a assinatura de cada método e função, que atua como uma especificação completa da funcionalidade implementada por um módulo ou fragmento de namespace. A sintaxe de uma assinatura de tipo é a mesma usada em declarações de métodos abstratos em interfaces e classes abstratas e também é mostrada pelo IntelliSense e pelo interpretador fsi.exe de F# quando ele exibe a entrada compilada corretamente.

Se não houver informações suficientes na assinatura de tipo para indicar se um tipo é lacrado ou se é um tipo de interface, será necessário incluir um atributo que indique a natureza do tipo ao compilador. Os atributos usados para essa finalidade são descritos na tabela a seguir.

Atributo Descrição
[<Sealed>] Para um tipo sem membros abstratos ou que não deve ser estendido.
[<Interface>] Para um tipo que é uma interface.

O compilador produz um erro quando os atributos não são consistentes entre a assinatura e a declaração no arquivo de implementação.

Use a palavra-chave val para criar uma assinatura para um valor ou valor de função. A palavra-chave type introduz uma assinatura de tipo.

É possível gerar um arquivo de assinatura usando a opção do compilador --sig. Em geral, você não grava os arquivos .fsi manualmente. Em vez disso, você os gera usando o compilador, adiciona esses arquivos ao projeto, quando há um, e os edita removendo métodos e funções que não deseja que sejam acessíveis.

Há diversas regras para assinaturas de tipo:

  • As abreviações de tipo em um arquivo de implementação não devem corresponder a um tipo sem abreviação em um arquivo de assinatura.

  • Registros e uniões discriminadas devem expor todos ou nenhum dos campos e construtores, e a ordem na assinatura deve corresponder à ordem no arquivo de implementação. As classes podem revelar alguns, todos ou nenhum de seus campos e métodos na assinatura.

  • As classes e estruturas com construtores devem expor as declarações de suas classes base (a declaração inherits). Além disso, classes e estruturas com construtores devem expor todos os seus métodos abstratos e declarações de interface.

  • Os tipos de interface devem revelar todos os métodos e interfaces.

As regras de assinaturas de valor são as seguintes:

  • Os modificadores de acessibilidade (public, internal e assim por diante) e os modificadores inline e mutable na assinatura devem corresponder aos da implementação.

  • Deve haver correspondência no número de parâmetros de tipo genérico (sejam inferidos implicitamente ou declarados explicitamente) e nos tipos e restrições de tipo nos parâmetros de tipo genérico.

  • Se o atributo Literal for usado, ele deverá aparecer na assinatura e na implementação e o mesmo valor literal deverá ser usado para ambas.

  • O padrão de parâmetros (também conhecido como aridade) de assinaturas e implementações deve ser consistente.

  • Se os nomes dos parâmetros em um arquivo de assinatura forem diferentes do arquivo de implementação correspondente, o nome no arquivo de assinatura será usado, o que pode causar problemas durante a depuração ou criação de perfil. Para ser notificado sobre essas incompatibilidades, habilite o aviso 3218 no arquivo de projeto ou ao invocar o compilador (confira --warnon em Opções do compilador).

O exemplo de código a seguir mostra um exemplo de um arquivo de assinatura com namespace, módulo, valor de função e assinaturas de tipo que tem os atributos apropriados. Também mostra o arquivo de implementação correspondente.

// Module1.fsi

namespace Library1
  module Module1 =
    val function1 : int -> int
    type Type1 =
        new : unit -> Type1
        member method1 : unit -> unit
        member method2 : unit -> unit

    [<Sealed>]
    type Type2 =
        new : unit -> Type2
        member method1 : unit -> unit
        member method2 : unit -> unit

    [<Interface>]
    type InterfaceType1 =
        abstract member method1 : int -> int
        abstract member method2 : string -> unit

O código a seguir mostra o arquivo de implementação.

namespace Library1

module Module1 =

    let function1 x = x + 1


    type Type1() =
        member type1.method1() =
            printfn "type1.method1"
        member type1.method2() =
            printfn "type1.method2"


    [<Sealed>]
    type Type2() =
        member type2.method1() =
            printfn "type2.method1"
        member type2.method2() =
            printfn "type2.method2"

    [<Interface>]
    type InterfaceType1 =
        abstract member method1 : int -> int
        abstract member method2 : string -> unit

Confira também