Signatures

Un fichier de signature contient des informations sur les signatures publiques d’un jeu d’éléments de programme F#, tels que des types, des espaces de noms et des modules. Il peut être utilisé pour spécifier l’accessibilité de ces éléments de programme.

Notes

Pour chaque fichier de code F#, vous pouvez avoir un fichier de signature, qui est un fichier portant le même nom que le fichier de code, mais avec l’extension .fsi au lieu de .fs. Les fichiers de signature peuvent également être ajoutés à la ligne de commande de compilation si vous utilisez la ligne de commande directement. Pour faire la distinction entre les fichiers de code et les fichiers de signature, les fichiers de code sont parfois appelés fichiers d’implémentation. Dans un projet, le fichier de signature doit précéder le fichier de code associé.

Un fichier de signature décrit les espaces de noms, modules, types et membres dans le fichier d’implémentation correspondant. Les informations dans un fichier de signature vous permettent de spécifier les parties du code dans le fichier d’implémentation correspondant auxquelles le code peut accéder à l’extérieur du fichier d’implémentation, ainsi que les parties qui sont internes au fichier d’implémentation. Les espaces de noms, modules et types inclus dans le fichier de signature doivent être un sous-ensemble des espaces de noms, des modules et des types inclus dans le fichier d’implémentation. À quelques exceptions près notées plus loin dans cette rubrique, les éléments de langage qui n’apparaissent pas dans le fichier de signature sont considérés comme privés pour le fichier d’implémentation. Si aucun fichier de signature n’est trouvé dans le projet ou la ligne de commande, l’accessibilité par défaut est utilisée.

Pour plus d’informations sur l’accessibilité par défaut, consultez Contrôle d’accès.

Dans un fichier de signature, vous ne répétez pas la définition des types et les implémentations de chaque méthode ou fonction. Au lieu de cela, vous utilisez la signature pour chaque méthode et fonction, qui sert de spécification complète des fonctionnalités implémentées par un fragment de module ou d’espace de noms. La syntaxe pour une signature de type est la même que celle utilisée dans les déclarations de méthode abstraites dans les interfaces et les classes abstraites, et est également indiquée par IntelliSense et l’interpréteur F# fsi.exe lorsqu’il affiche correctement l’entrée compilée.

Quand il n’y a pas assez d’informations dans la signature de type pour indiquer si un type est sealed ou non, ou s’il s’agit d’un type d’interface, vous devez ajouter un attribut qui indique la nature du type au compilateur. Les attributs utilisés à cette fin sont décrits dans le tableau suivant.

Attribut Description
[<Sealed>] Type qui n’a pas de membres abstraits ou qui ne doit pas être étendu.
[<Interface>] Type qui est une interface.

Le compilateur produit une erreur si les attributs ne sont pas cohérents entre la signature et la déclaration dans le fichier d’implémentation.

Utilisez le mot clé val pour créer une signature pour une valeur ou une valeur de fonction. Le mot clé type introduit une signature de type.

Vous pouvez générer un fichier de signature à l’aide de l’option de compilateur --sig . En général, vous n’écrivez pas les fichiers .fsi manuellement. À la place, vous générez des fichiers .fsi à l’aide du compilateur, vous les ajoutez à votre projet, si vous en avez un, et vous les modifiez en supprimant les méthodes et les fonctions que vous ne souhaitez pas rendre accessibles.

Les signatures de type sont soumises à plusieurs règles :

  • Les abréviations de type dans un fichier d’implémentation ne doivent pas correspondre à un type sans abréviation dans un fichier de signature.

  • Les enregistrements et les unions discriminées doivent exposer la totalité ou aucun de leurs champs et constructeurs, et l’ordre dans la signature doit correspondre à celui dans le fichier d’implémentation. Les classes peuvent révéler une partie, la totalité ou aucun des champs et des méthodes dans la signature.

  • Les classes et les structures qui ont des constructeurs doivent exposer les déclarations de leurs classes de base (déclaration inherits ). En outre, les classes et les structures qui ont des constructeurs doivent exposer toutes leurs méthodes abstraites et leurs déclarations d’interface.

  • Les types d’interface doivent révéler toutes leurs méthodes et interfaces.

Les signatures de valeur obéissent aux règles suivantes :

  • Les modificateurs d’accessibilité (public, internal, etc.) et les modificateurs inline et mutable dans la signature doivent correspondre à ceux présents dans l’implémentation.

  • Le nombre de paramètres de type générique (déduits implicitement ou déclarés explicitement) doit correspondre, et les types et contraintes de type dans les paramètres de type générique doivent correspondre.

  • Si l’attribut Literal est utilisé, il doit figurer dans la signature et l’implémentation, et la même valeur littérale doit être utilisée pour les deux.

  • Le modèle de paramètres (aussi appelé arité) des signatures et des implémentations doit être cohérent.

  • Si les noms de paramètre dans un fichier de signature diffèrent de ceux du fichier d’implémentation correspondant, le nom dans le fichier de signature est utilisé à la place, ce qui peut entraîner des problèmes pour le débogage ou le profilage. Si vous voulez être informé de ces incompatibilités, activez l’avertissement 3218 dans votre fichier projet ou quand vous appelez le compilateur (consultez --warnon sous Options du compilateur).

Le code suivant illustre un exemple de fichier de signature contenant des signatures d’espace de noms, de module, de valeur de fonction et de type avec les attributs appropriés. Il indique également le fichier d’implémentation correspondant.

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

Le code suivant représente le fichier d’implémentation.

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

Voir aussi