Classes (F#)

Les classes sont des types qui représentent des objets pouvant avoir des propriétés, des méthodes et des événements.

Syntaxe

// 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’objet .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. type-params décrit les paramètres de type générique facultatifs. Il se compose de noms de paramètres de type et de contraintes placés entre crochets pointus (< et >). Pour plus d’informations, consultez Génériques et Contraintes. parameter-list décrit les paramètres de constructeur. Le premier modificateur d’accès concerne le type. Le second concerne le constructeur principal. Dans les deux cas, la valeur par défaut est public.

Vous spécifiez la classe de base d’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 locaux par rapport à 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 au moment de la construction d’objet.

member-list se compose de constructeurs supplémentaires, de déclarations d’instance et de méthode statique, de déclarations d’interface, de liaisons abstraites ainsi que de déclarations de propriété et d’événement. Vous trouverez leur description dans Membres.

Le identifier utilisé avec le mot clé facultatif as donne un nom à la variable d’instance, ou auto-identificateur, qui est utilisable 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 mutuellement, sont joints à l’aide du mot clé and, tout comme les fonctions mutuellement récursives. Pour obtenir un exemple, consultez la section Types mutuellement récursifs.

Constructeurs

Le constructeur représente du code qui crée une instance du type de classe. Les constructeurs des classes fonctionnent un peu différemment en F# par rapport aux autres langages .NET. Dans une classe F#, il existe toujours un constructeur principal dont les arguments sont décrits dans le parameter-list qui suit le nom de type, et dont le corps comprend les liaisons let (et let rec) au début de la déclaration de classe ainsi que les liaisons do qui suivent. Les arguments du constructeur principal sont présents dans l’étendue de la déclaration de classe.

Vous pouvez ajouter des constructeurs supplémentaires en utilisant le mot clé new pour ajouter un membre, comme ceci :

new(argument-list) = constructor-body

Le corps du nouveau constructeur doit appeler le constructeur principal 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 accepte deux arguments, et un autre constructeur qui n’accepte aucun argument.

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. Elles s’exécutent donc chaque fois qu’une instance de classe est créée. Si une liaison let est une fonction, elle est compilée en 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 en une variable locale par rapport au constructeur. Sinon, elle est compilée en 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. Dans la mesure où les constructeurs supplémentaires appellent toujours le constructeur principal, les liaisons let et do s’exécutent toujours, quel que soit le constructeur appelé.

Les champs créés par les liaisons let sont accessibles via toutes les méthodes et propriétés de la classe. Toutefois, ils ne sont pas accessibles à partir de méthodes statiques, même si celles-ci acceptent une variable d’instance en tant que paramètre. Ils ne sont pas accessibles à l’aide de l’auto-identificateur, s’il existe.

Auto-identificateurs

Un auto-identificateur est un nom qui représente l’instance actuelle. Les auto-identificateurs ressemblent au mot clé this en C# ou en C++, ou au mot clé Me en Visual Basic. Vous pouvez définir un auto-identificateur de deux façons différentes, selon que vous souhaitez que l’auto-identificateur se trouve dans l’étendue de la définition de classe entière, ou simplement dans l’étendue d’une méthode individuelle.

Si vous souhaitez définir un auto-identificateur pour l’ensemble de la classe, utilisez le mot clé as après les parenthèses fermantes de la liste des paramètres de constructeur, puis spécifiez le nom de l’identificateur.

Si vous souhaitez définir un auto-identificateur pour une seule méthode, spécifiez l’auto-identificateur dans la déclaration de membre, juste avant le nom de la méthode avec un point (.) en tant que 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 permet de définir l’auto-identificateur. À la cinquième ligne, l’identificateur this permet de définir un auto-identificateur dont l’étendue est limitée à 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 vous le souhaitez. Vous n’êtes pas limité aux noms tels que self, Me ou this.

L’auto-identificateur déclaré avec le mot clé as n’est initialisé qu’après le constructeur de base. Ainsi, quand il est utilisé avant ou à l’intérieur du constructeur de base, l’exception suivante est levée au moment de l’exécution : System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.. Vous pouvez utiliser l’auto-identificateur librement après le constructeur de base, par exemple dans les liaisons let ou do.

Paramètres de type générique

Les paramètres de type générique sont spécifiés entre crochets pointus (< et >), sous la forme d’un guillemet simple suivi d’un identificateur. Plusieurs paramètres de type générique sont séparés par des virgules. Le paramètre de type générique est présent dans l’étendue de 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 quand 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, le cas échéant. En F#, une seule classe de base directe est autorisée. Les interfaces implémentées par une classe ne sont pas considérées comme des classes de base. Les interfaces sont traitées dans la rubrique Interfaces.

Vous pouvez accéder aux méthodes et aux propriétés de la classe de base à partir de la classe dérivée en utilisant le mot clé de langage base en tant qu’identificateur, suivi d’un point (.) et du nom du membre.

Pour plus d’informations, consultez Héritage.

Section des membres

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 dans cette section. Les liaisons let et do ne peuvent pas apparaître dans cette section. Dans la mesure où vous pouvez ajouter les membres à divers types F# en plus des classes, ils sont traités dans une rubrique distincte, Membres.

Types mutuellement récursifs

Quand vous définissez des types qui se référencent mutuellement de manière circulaire, vous chaînez les définitions de type à l’aide du mot clé and. Le mot clé and remplace le mot clé type pour toutes les définitions sauf la première, comme indiqué ci-après.

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 du répertoire actif.

Quand utiliser des classes, des unions, des enregistrements et des structures

Compte tenu de la variété des types disponibles, vous devez avoir une bonne compréhension de la finalité de chaque type pour sélectionner le type approprié à une situation particulière. Les classes sont conçues pour être utilisées dans des 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 en étroite collaboration avec le .NET Framework ou toute autre bibliothèque orientée objet, et en particulier si vous devez l’étendre à partir d’un système de type orienté objet telle qu’une bibliothèque d’IU, les classes sont probablement appropriées.

Si vous n’interagissez pas étroitement avec du code orienté objet, ou si vous écrivez du code autonome et donc protégé contre les interactions fréquentes avec du code orienté objet, vous devez utiliser un mélange de classes, d’enregistrements et d’unions discriminées. Une seule union discriminée bien pensée ainsi que du code approprié incluant des critères spéciaux, peuvent souvent représenter une alternative plus simple à une hiérarchie d’objets. Pour plus d’informations sur les unions discriminées, consultez Unions discriminées.

Les enregistrements ont l’avantage d’être plus simples que les classes, mais ils ne sont pas appropriés quand les demandes d’un type dépassent ce qui peut être accompli en raison de leur simplicité. Les enregistrements sont essentiellement de simples agrégats de valeurs, sans constructeurs distincts pouvant effectuer des actions personnalisées, sans champs masqués et sans implémentations d’héritage ou d’interface. Bien que vous puissiez ajouter des membres tels que des propriétés et des méthodes aux enregistrements pour rendre leur comportement plus complexe, les champs stockés dans un enregistrement sont toujours un simple agrégat de valeurs. Pour plus d’informations sur les enregistrements, consultez Enregistrements.

Les structures sont également utiles pour les petits agrégats de données, mais elles diffèrent des classes et des enregistrements dans la mesure où il s’agit de types valeur .NET. Les classes et les enregistrements sont des types référence .NET. La sémantique des types valeur et des types référence est différente dans la mesure où les types valeur sont passés par valeur. Cela signifie qu’ils sont copiés bit pour bit quand ils sont passés sous forme de paramètre, ou quand ils sont retournés à partir d’une fonction. Ils sont également stockés dans la pile ou, s’ils sont utilisés sous forme de champ, ils sont incorporés dans l’objet parent au lieu d’être stockés à leur propre emplacement distinct dans le tas. Les structures conviennent donc aux données faisant l’objet d’accès fréquents quand la surcharge liée à l’accès au tas constitue un problème. Pour plus d’informations sur les structures, consultez Structs.

Voir aussi