Constructeurs

Cet article décrit comment définir et utiliser des constructeurs pour créer et initialiser des objets de classe et de structure.

Construction d’objets de classe

Les objets de types de classe ont des constructeurs. Il existe deux sortes de constructeurs. Le premier est le constructeur principal, dont les paramètres apparaissent entre parenthèses juste après le nom du type. Vous spécifiez d’autres constructeurs supplémentaires facultatifs à l’aide du mot clé new. Ces constructeurs supplémentaires doivent appeler le constructeur principal.

Le constructeur principal contient des liaisons let et do qui apparaissent au début de la définition de classe. Une liaison let déclare des champs privés et des méthodes de la classe, une liaison do exécute du code. Pour plus d’informations sur les liaisons let dans les constructeurs de classe, consultez Liaisons let dans les classes. Pour plus d’informations sur les liaisons do dans les constructeurs de classe, consultez Liaisons do dans les classes.

Que le constructeur que vous souhaitez appeler soit un constructeur principal ou un constructeur supplémentaire, vous pouvez créer des objets à l’aide d’une expression new, avec ou sans le mot clé new facultatif. Vous initialisez vos objets avec des arguments de constructeur, soit en répertoriant les arguments dans l’ordre et séparés par des virgules et placés entre parenthèses, soit en utilisant des arguments nommés et des valeurs entre parenthèses. Vous pouvez également définir des propriétés sur un objet pendant la construction de l’objet en utilisant les noms de propriété et en affectant des valeurs tout comme vous utilisez des arguments de constructeur nommé.

Le code suivant illustre une classe qui a un constructeur et différentes façons de créer des objets :

// This class has a primary constructor that takes three arguments
// and an additional constructor that calls the primary constructor.
type MyClass(x0, y0, z0) =
    let mutable x = x0
    let mutable y = y0
    let mutable z = z0
    do
        printfn "Initialized object that has coordinates (%d, %d, %d)" x y z
    member this.X with get() = x and set(value) = x <- value
    member this.Y with get() = y and set(value) = y <- value
    member this.Z with get() = z and set(value) = z <- value
    new() = MyClass(0, 0, 0)

// Create by using the new keyword.
let myObject1 = new MyClass(1, 2, 3)
// Create without using the new keyword.
let myObject2 = MyClass(4, 5, 6)
// Create by using named arguments.
let myObject3 = MyClass(x0 = 7, y0 = 8, z0 = 9)
// Create by using the additional constructor.
let myObject4 = MyClass()

La sortie se présente comme suit :

Initialized object that has coordinates (1, 2, 3)
Initialized object that has coordinates (4, 5, 6)
Initialized object that has coordinates (7, 8, 9)
Initialized object that has coordinates (0, 0, 0)

Construction de structures

Les structures suivent toutes les règles des classes. Par conséquent, vous pouvez avoir un constructeur principal et vous pouvez fournir des constructeurs supplémentaires à l’aide de new. Toutefois, il existe une différence importante entre les structures et les classes : les structures peuvent avoir un constructeur sans paramètre (c’est-à-dire sans arguments) même si aucun constructeur principal n’est défini. Le constructeur sans paramètre initialise tous les champs à la valeur par défaut de ce type, généralement zéro ou son équivalent. Tous les constructeurs que vous définissez pour les structures doivent avoir au moins un argument afin qu’ils n’entrent pas en conflit avec le constructeur sans paramètre.

En outre, les structures ont souvent des champs qui sont créés à l’aide du mot clé val, les classes peuvent également avoir ces champs. Les structures et les classes qui ont des champs définis à l’aide du mot clé val peuvent également être initialisées dans des constructeurs supplémentaires à l’aide d’expressions d’enregistrement, comme indiqué dans le code suivant.

type MyStruct =
    struct
       val X : int
       val Y : int
       val Z : int
       new(x, y, z) = { X = x; Y = y; Z = z }
    end

let myStructure1 = new MyStruct(1, 2, 3)

Pour plus d’informations, consultez Champs explicites : mot clé val.

Exécution d’effets secondaires dans les constructeurs

Un constructeur principal d’une classe peut exécuter du code dans une liaison do. Toutefois, que se passe-t-il si vous devez exécuter du code dans un constructeur supplémentaire, sans liaison do ? Pour ce faire, utilisez le mot clé then.

 // Executing side effects in the primary constructor and
// additional constructors.
type Person(nameIn : string, idIn : int) =
    let mutable name = nameIn
    let mutable id = idIn
    do printfn "Created a person object."
    member this.Name with get() = name and set(v) = name <- v
    member this.ID with get() = id and set(v) = id <- v
    new() =
        Person("Invalid Name", -1)
        then
            printfn "Created an invalid person object."

let person1 = new Person("Humberto Acevedo", 123458734)
let person2 = new Person()

Les effets secondaires du constructeur principal s’exécutent toujours. La sortie est donc la suivante :

Created a person object.
Created a person object.
Created an invalid person object.

La raison pour laquelle then est obligatoire au lieu d’un autre do est que le mot clé do a sa signification standard de limiter une expression unit-returning lorsqu’elle est présente dans le corps d’un constructeur supplémentaire. Il a uniquement une signification particulière dans le contexte des constructeurs principaux.

Auto-identificateurs dans les constructeurs

Dans les autres membres, vous fournissez un nom pour l’objet actuel dans la définition de chaque membre. Vous pouvez également placer l’auto-identificateur sur la première ligne de la définition de classe à l’aide du mot clé as immédiatement après les paramètres du constructeur. L’exemple de code suivant illustre cette syntaxe.

type MyClass1(x) as this =
    // This use of the self identifier produces a warning - avoid.
    let x1 = this.X
    // This use of the self identifier is acceptable.
    do printfn "Initializing object with X =%d" this.X
    member this.X = x

Dans d’autres constructeurs, vous pouvez également définir un auto-identificateur en plaçant la clause as juste après les paramètres du constructeur. L’exemple de code suivant illustre cette syntaxe :

type MyClass2(x : int) =
    member this.X = x
    new() as this = MyClass2(0) then printfn "Initializing with X = %d" this.X

Des problèmes peuvent survenir lorsque vous essayez d’utiliser un objet avant qu’il ne soit entièrement défini. Par conséquent, les utilisations de l’auto-identificateur peuvent amener le compilateur à émettre un avertissement et à insérer des vérifications supplémentaires pour s’assurer que les membres d’un objet ne sont pas accessibles avant l’initialisation de l’objet. Vous devez utiliser l’auto-identificateur uniquement dans les liaisons do du constructeur principal, ou après le mot clé then dans les constructeurs supplémentaires.

Le nom de l’auto-identificateur ne doit pas être this. Il peut s’agir de n’importe quel identificateur valide.

Assignation de valeurs aux propriétés lors de l’initialisation

Vous pouvez attribuer des valeurs aux propriétés d’un objet de classe dans le code d’initialisation en ajoutant une liste d’affectations du formulaire property = value à la liste d’arguments d’un constructeur. Cela est illustré par l'exemple de code suivant :

 type Account() =
    let mutable balance = 0.0
    let mutable number = 0
    let mutable firstName = ""
    let mutable lastName = ""
    member this.AccountNumber
       with get() = number
       and set(value) = number <- value
    member this.FirstName
       with get() = firstName
       and set(value) = firstName <- value
    member this.LastName
       with get() = lastName
       and set(value) = lastName <- value
    member this.Balance
       with get() = balance
       and set(value) = balance <- value
    member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
    member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(AccountNumber=8782108,
                           FirstName="Darren", LastName="Parker",
                           Balance=1543.33)

La version suivante du code précédent illustre la combinaison d’arguments ordinaires, d’arguments facultatifs et de paramètres de propriété dans un appel de constructeur :

type Account(accountNumber : int, ?first: string, ?last: string, ?bal : float) =
   let mutable balance = defaultArg bal 0.0
   let mutable number = accountNumber
   let mutable firstName = defaultArg first ""
   let mutable lastName = defaultArg last ""
   member this.AccountNumber
      with get() = number
      and set(value) = number <- value
   member this.FirstName
      with get() = firstName
      and set(value) = firstName <- value
   member this.LastName
      with get() = lastName
      and set(value) = lastName <- value
   member this.Balance
      with get() = balance
      and set(value) = balance <- value
   member this.Deposit(amount: float) = this.Balance <- this.Balance + amount
   member this.Withdraw(amount: float) = this.Balance <- this.Balance - amount


let account1 = new Account(8782108, bal = 543.33,
                          FirstName="Raman", LastName="Iyer")

Constructeurs dans la classe héritée

Lorsque vous héritez d’une classe de base qui a un constructeur, vous devez spécifier ses arguments dans la clause hériter. Pour plus d’informations, consultez Constructeurs et héritage.

Constructeurs statiques ou constructeurs de types

En plus de spécifier du code pour la création d’objets, les liaisons statiques let et do peuvent être créées dans des types de classes qui s’exécutent avant que le type ne soit utilisé pour effectuer l’initialisation au niveau du type. Pour plus d’informations, consultez Liaisons let dans les classes et Liaisons do dans les classes.

Voir aussi