Classes (F#)
Les classes sont des types représentant des objets qui peuvent avoir des propriétés, des méthodes et des événements.
// 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 ...
...
Notes
Les classes représentent la description fondamentale des types d'objets .NET ; la classe est le concept de type principal qui prend en charge la programmation orientée objet en F#.
Dans la syntaxe précédente, type-name est un identificateur valide quelconque.type-params décrit les paramètres de type générique facultatifs.Il se compose de noms et de contraintes de paramètre de type dans des crochets pointus (< et >).Pour plus d'informations, consultez Génériques (F#) et Contraintes (F#).parameter-list décrit les paramètres de constructeur.Le premier modificateur d'accès associé au type ; le deuxième est associé au constructeur principal.Dans les deux cas, la valeur par défaut est public.
Vous spécifiez la classe de base pour une classe à l'aide du mot clé inherit.Vous devez fournir des arguments, entre parenthèses, pour le constructeur de classe de base.
Vous déclarez des champs ou des valeurs de fonction qui sont locales vis-à-vis de la classe à l'aide de liaisons let, et vous devez suivre les règles générales pour les liaisons let.La section do-bindings comprend le code à exécuter après la construction d'objet.
member-list se compose de constructeurs supplémentaires, de déclarations de méthodes d'instance et statique, de déclarations d'interface, de liaisons abstraites, et de déclarations de propriété et d'événement.Vous en trouverez la description dans la section Membres (F#).
L'identifier qui est utilisé avec le mot clé as facultatif donne un nom à la variable d'instance, ou auto-identificateur, que vous pouvez utiliser dans la définition de type pour faire référence à l'instance du type.Pour plus d'informations, consultez la section Auto-identificateurs plus loin dans cette rubrique.
Les mots clés class et end qui marquent le début et la fin de la définition sont facultatifs.
Les types mutuellement récursifs, qui sont des types qui se référencent les uns les autres, sont joints au mot clé and (comme les fonctions mutuellement récursives).Pour obtenir un exemple, consultez la section Types mutuellement récursifs.
Constructeurs
Le constructeur constitue du code qui crée une instance du type de classe.En F#, les constructeurs pour les classes ne fonctionnent pas tout à fait de la même façon que dans d'autres langages .NET.Dans une classe F#, il y a toujours un constructeur principal dont les arguments sont décrits dans le parameter-list qui suit le nom de type, et dont le corps se compose des liaisons let (et let rec) au début de la déclaration de classe et des liaisons do qui suivent.Les arguments du constructeur principal sont dans la portée dans toute la déclaration de classe.
Vous pouvez ajouter des constructeurs supplémentaires en utilisant le mot clé new pour ajouter un membre, comme suit :
new(argument-list) = constructor-body
Le corps du nouveau constructeur doit appeler le constructeur principal qui est spécifié en haut de la déclaration de classe.
L'exemple suivant illustre ce concept.Dans le code suivant, MyClass a deux constructeurs : un constructeur principal qui prend deux arguments, et un autre constructeur qui ne prend pas d'arguments.
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
Liaisons let et do
Les liaisons let et do dans une définition de classe forment le corps du constructeur de classe principal ; par conséquent, elles s'exécutent chaque fois qu'une instance de classe est créée.Si une liaison let est une fonction, elle est compilée dans un membre.Si la liaison let est une valeur qui n'est pas utilisée dans une fonction ou un membre, elle est compilée dans une variable qui est locale au constructeur.Sinon, elle est compilée dans un champ de la classe.Les expressions do qui suivent sont compilées dans le constructeur principal et exécutent le code d'initialisation pour chaque instance.Étant donné que les constructeurs supplémentaires appellent toujours le constructeur principal, les liaisons let et do s'exécutent toujours indépendamment du constructeur qui est appelé.
Les champs créés par les liaisons let sont accessibles dans l'ensemble des méthodes et des propriétés de la classe ; toutefois, ils sont inaccessibles à partir de méthodes statiques, même si les méthodes statiques prennent une variable d'instance comme paramètre.Ils sont inaccessibles à l'aide de l'auto-identificateur, s'il en existe un.
Auto-identificateurs
Un auto-identificateur est un nom qui représente l'instance actuelle.Les auto-identificateurs sont similaires au mot clé this en C# ou en C++, et à Me en Visual Basic.Vous pouvez définir un auto-identificateur de deux manières différentes, selon que celui-ci se trouve dans la portée pour la définition de classe entière ou simplement pour une méthode individuelle.
Pour définir un auto-identificateur pour la classe entière, utilisez le mot clé as après les parenthèses fermantes de la liste de paramètres du constructeur, et spécifiez le nom de l'identificateur.
Pour définir un auto-identificateur pour une seule méthode, indiquez l'auto-identificateur dans la déclaration de membre, juste avant le nom de la méthode, avec un point (.) comme séparateur.
L'exemple de code suivant illustre les deux façons de créer un auto-identificateur.Dans la première ligne, le mot clé as est utilisé pour définir l'auto-identificateur.Dans la cinquième ligne, l'identificateur this est utilisé pour définir un auto-identificateur dont la portée est restreinte à la méthode PrintMessage.
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
Contrairement à d'autres langages .NET, vous pouvez nommer l'auto-identificateur comme bon vous semble ; vous n'êtes pas limité à certains noms tels que self, Me ou this.
L'auto-identificateur qui est déclaré avec le mot clé as n'est initialisé qu'après l'exécution des liaisons let.Par conséquent, il ne peut pas être utilisé dans les liaisons let.Vous pouvez utiliser l'auto-identificateur dans la section des liaisons do.
Paramètres de type générique
Les paramètres de type générique sont spécifiés dans des crochets pointus (< et >), sous forme d'un guillemet simple suivi d'un identificateur.Vous pouvez spécifier plusieurs paramètres de type générique en les séparant par des virgules.Le paramètre de type générique est dans la portée dans toute la déclaration.L'exemple de code suivant montre comment spécifier des paramètres de type générique.
type MyGenericClass<'a> (x: 'a) =
do printfn "%A" x
Les arguments de type sont déduits lorsque le type est utilisé.Dans le code suivant, le type déduit est une séquence de tuples.
let g1 = MyGenericClass( seq { for i in 1 .. 10 -> (i, i*i) } )
Spécification de l'héritage
La clause inherit identifie la classe de base directe, s'il y en a une.En F#, une seule classe de base directe est autorisée.Les interfaces qu'une classe implémente ne sont pas considérées comme des classes de base.Les interfaces sont traitées dans la rubrique Interfaces (F#).
Vous pouvez accéder aux méthodes et aux propriétés de la classe de base à partir de la classe dérivée à l'aide du mot clé de langage base comme identificateur, suivi d'un point (.) et du nom du membre.
Pour plus d’informations, consultez Héritage (F#).
Section de membres
Dans cette section, vous pouvez définir des méthodes statiques ou d'instance, des propriétés, des implémentations d'interface, des membres abstraits, des déclarations d'événement et des constructeurs supplémentaires.Les liaisons let et do ne peuvent pas apparaître dans cette section.Étant donné que les membres peuvent être ajoutés à divers types F# en plus des classes, ils sont traités dans la rubrique séparée suivante : Membres (F#).
Types mutuellement récursifs
Lorsque vous définissez des types qui se référencent les uns les autres de façon circulaire, vous regroupez dans une chaîne les définitions de type à l'aide du mot clé and.Le mot clé and remplace le mot clé type dans toutes les définitions, sauf la première, comme suit.
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
La sortie est une liste de tous les fichiers dans le répertoire actif.
Quand utiliser des classes, des unions, des enregistrements et des structures
Compte tenu du nombre de types disponibles, vous devez bien comprendre le rôle de chaque type afin de pouvoir sélectionner le type adapté à une situation particulière.Les classes sont conçues pour une utilisation dans les contextes de programmation orientée objet.La programmation orientée objet est le paradigme dominant utilisé dans les applications écrites pour le .NET Framework.Si votre code F# doit fonctionner étroitement avec le .NET Framework ou une autre bibliothèque orientée objet, et plus particulièrement si vous devez étendre le code à partir d'un système de type orienté objet tel qu'une bibliothèque d'interface utilisateur, le choix de classes est probablement approprié.
Si vous n'interagissez pas étroitement avec du code orienté objet, ou si vous écrivez du code qui est autonome et par conséquent écarté de l'interaction fréquente avec le code orienté objet, songez à utiliser des enregistrements et des unions discriminées.Une union discriminée unique et bien conçue, associée à des critères spéciaux appropriés, peut souvent servir d'alternative plus simple à une hiérarchie d'objets.Pour plus d'informations sur les unions discriminées, consultez Unions discriminées (F#).
Les enregistrements ont l'avantage d'être plus simples que les classes, mais leur utilisation n'est pas justifiée lorsque les demandes d'un type dépassent ce qu'ils permettent d'accomplir du fait de leur simplicité.Les enregistrements sont essentiellement des agrégats simples de valeurs, sans constructeurs séparés pouvant effectuer des actions personnalisées, sans champs masqués, et sans implémentations d'héritage ou d'interface.Bien que des membres tels que des propriétés et des méthodes peuvent être ajoutés à des enregistrements pour rendre leur comportement plus complexe, les champs stockés dans un enregistrement restent un agrégat simple de valeurs.Pour plus d'informations sur les enregistrements, consultez Enregistrements (F#).
Les structures sont également utiles pour les petits agrégats de données, mais elles se distinguent des classes et des enregistrements par le fait qu'elles sont des types valeur .NET.Les classes et les enregistrements sont des types référence.La sémantique des types valeur diffère de celle des types référence en ce sens que les types valeur sont passés par valeur.Cela signifie qu'ils sont copiés bit par bit lorsqu'ils sont passés comme paramètre ou retournés d'une fonction.Ils sont également stockés sur la pile ou, s'ils sont utilisés en tant que champ, incorporés dans l'objet parent au lieu d'être stockés dans leur propre emplacement distinct sur le tas.Par conséquent, les structures sont appropriées pour les données fréquemment utilisées lorsque la charge liée à l'accès du tas pose problème.Pour plus d'informations sur les structures, consultez Structures (F#).