Formation F#
  • Intro
  • Bases
    • Le F♯, c'est quoi ?
    • Syntaxe
    • Premiers concepts
    • 🍔 Quiz
  • Fonctions
    • Signature
    • Fonctions
    • Fonctions standard
    • Opérateurs
    • Fonctions : compléments
    • 🍔 Quiz
    • 📜 Récap’
  • Types composites
    • Généralités
    • Tuples
    • Records
    • Unions
    • Enums
    • Records anonymes
    • Types valeur
    • 🍔 Quiz
  • Types : Compléments
    • Type unit
    • Génériques
    • Types flexibles
    • Unités de mesure
    • Conversion
    • Exceptions F#
  • Pattern matching
    • Patterns
    • Match expression
    • 🚀 Active Patterns
    • 📜 Récap’
  • Collections
    • Vue d'ensemble
    • Types
    • Fonctions génériques
    • Fonctions spécifiques
    • 🍔 Quiz
    • 📜 Récap’
  • Programmation asynchrone
    • Workflow asynchrone
    • Interop avec la TPL .NET
    • 📜 Récap’
  • Types monadiques
    • Type Option
    • Type Result
    • Smart constructor
    • 🚀 Computation expression (CE)
    • 🚀 CE - Fondements théoriques
    • 📜 Récap’
  • Module & namespace
    • Vue d'ensemble
    • Namespace
    • Module
    • 🍔 Quiz
    • 📜 Récap’
  • Orienté-objet
    • Introduction
    • Membres
    • Extensions de type
    • Classe, structure
    • Interface
    • Expression objet
    • Recommandations
  • 🦚 Aller plus loin
Propulsé par GitBook
Sur cette page
  • Classe
  • Classe générique
  • Constructeur secondaire
  • Instanciation
  • Initialisation des propriétés
  • Classe vide
  • Singleton
  • Classe abstraite
  • Champs
  • Champ implicite
  • D'instance
  • Statique
  • Champ explicite
  • Champ vs propriété
  • Champ explicite ou implicite ou propriété
  • Structures

Cet article vous a-t-il été utile ?

Modifier sur GitHub
  1. Orienté-objet

Classe, structure

Classe

Classe en F♯ ≡ classe en C♯

  • Brique de base pour l'orienté-objet

  • Constructeur d'objets contenant des données de type défini et des méthodes

Définition d'une classe

  • Commence par type (comme tout type en F♯)

  • Nom de la classe généralement suivi du constructeur primaire

type CustomerName(firstName: string, lastName: string) =
    // Corps du constructeur primaire
    // Membres...

☝ Paramètres firstName et lastName visibles dans tout le corps de la classe

Classe générique

Paramètres génériques à spécifier car non inférés

type Tuple2_KO(item1, item2) = // ⚠️ 'item1' et 'item2': type 'obj' !
    // ...

type Tuple2<'T1, 'T2>(item1: 'T1, item2: 'T2) =  // 👌
    // ...

Constructeur secondaire

Syntaxe pour définir un autre constructeur : new(argument-list) = constructor-body

☝ Doit appeler le constructeur primaire !

type Point(x: float, y: float) =
    new() = Point(0, 0)
    // Membres...

☝ Paramètres des constructeurs : que en tuple, pas curryfiés !

Instanciation

Appel d'un des constructeurs, avec arguments en tuple

  • Ne pas oublier () si aucun argument, sinon on obtient une fonction !

Dans un let binding : new optionnel et non recommandé

  • let v = Vector(1.0, 2.0) 👌

  • let v = new Vector(1.0, 2.0) ❌

Dans un use binding : new obligatoire

  • use d = new Disposable()

Initialisation des propriétés

On peut initialiser des propriétés avec setter à l'instanciation

  • Les spécifier en tant que arguments nommés dans l'appel au constructeur

  • Les placer après les éventuels arguments du constructeur :

type PersonName(first: string) =
    member val First = first with get, set
    member val Last = "" with get, set

let p1 = PersonName("John")
let p2 = PersonName("John", Last="Paul")
let p3 = PersonName(first="John", Last="Paul")

💡 Équivalent de la syntaxe C♯ new PersonName("John") { Last = "Paul" }

Classe vide

Pour définir une classe sans corps, on utilise la syntaxe verbeuse class end :

type K = class end
let k = K()

Singleton

On peut définir une classe Singleton en rendant privé le constructeur primaire :

type S private() =
    static member val Instance = S()

let s = S.Instance  // val s : S

💡 On peut utiliser simplement une single-case union :

type S = S
let s = S

Classe abstraite

Annotée avec [<AbstractClass>]

Un des membres est abstrait :

  1. Déclaré avec mot clé abstract

  2. Pas d'implémentation par défaut avec mot clé default (Sinon le membre est virtuel)

Héritage via mot clé inherit, suivi de l'appel au constructeur de la classe de base

Exemple :

[<AbstractClass>]
type Shape2D() =
    member val Center = (0.0, 0.0) with get, set
    member this.Move(?deltaX: float, ?deltaY: float) =
        let x, y = this.Center
        this.Center <- (x + defaultArg deltaX 0.0,
                        y + defaultArg deltaY 0.0)
    abstract GetArea : unit -> float
    abstract Perimeter : float  with get

type Square(side) =
    inherit Shape2D()
    member val Side = side
    override _.GetArea () = side * side
    override _.Perimeter = 4.0 * side

let o = Square(side = 2.0, Center = (1.0, 1.0))
printfn $"S={o.Side}, A={o.GetArea()}, P={o.Perimeter}"  // S=2, A=4, P=8
o.Move(deltaY = -2.0)
printfn $"Center {o.Center}"  // Center (1, -1)

Champs

Convention de nommage : camelCase

2 types de champs : implicite ou explicite

  • Implicite ≃ Variable à l'intérieur du constructeur primaire

  • Explicite ≡ Champ classique d'une classe en C♯ / Java

Champ implicite

Syntaxe :

  • Variable : [static] let [ mutable ] variable-name = expression

  • Fonction : [static] let [ rec ] function-name function-args = expression

☝ Notes

  • Déclaré avant les membres de la classe

  • Valeur initiale obligatoire

  • Privé

  • S'utilise sans devoir préfixer par le self-identifier

D'instance

type Person(firstName: string, lastName: string) =
    let fullName = $"{firstName} {lastName}"
    member _.Hi() = printfn $"Hi, I'm {fullName}!"

let p = Person("John", "Doe")
p.Hi()  // Hi, I'm John Doe!

Statique

type K() =
    static let mutable count = 0

    // do binding exécuté à chaque construction
    do
        count <- count + 1

    member _.CreatedCount = count

let k1 = K()
let count1 = k1.CreatedCount  // 1
let k2 = K()
let count2 = k2.CreatedCount  // 2

Champ explicite

Déclaration du type, sans valeur initiale : val [ mutable ] [ access-modifier ] field-name : type-name

  • val mutable a: int → champ publique

  • val a: int → champ interne a@ + propriété a => a@

Champ vs propriété

// Champs explicites readonly
type C1 =
    val a: int
    val b: int
    val mutable c: int
    new(a, b) = { a = a; b = b; c = 0 } // 💡 Constructeur 2ndaire "compacte"

// VS propriétés readonly => ordre inversé dans SharpLab : b avant a
type C2(a: int, b: int) =
    member _.A = a
    member _.B = b
    member _.C = 0

// VS propriétés auto-implémentées
type C3(a: int, b: int) =
    member val A = a
    member val B = b with get
    member val C = 0 with get, set

Champ explicite ou implicite ou propriété

Champ explicite peu utilisé :

  • Ne concerne que les classes et structures

  • Besoin d'une variable [<ThreadStatic>]

  • Interaction avec classe F♯ de code généré sans constructeur primaire

Champ implicite - let binding

  • Variable intermédiaire lors de la construction

Autres cas d'usages → propriété auto-implémentée

  • Exposer une valeur → member val

  • Exposer un "champ" mutable → member val ... with get, set

Structures

Alternatives aux classes mais + limités / héritage et récursivité

Même syntaxe que pour les classes mais avec en plus :

  • Soit attribut [<Struct>]

  • Soit bloc struct...end (fréquent)

type Point =
    struct
        val mutable X: float
        val mutable Y: float
        new(x, y) = { X = x; Y = y }
    end

let x = Point(1.0, 2.0)
PrécédentExtensions de typeSuivantInterface

Dernière mise à jour il y a 2 ans

Cet article vous a-t-il été utile ?

Pas thread safe !

Utile avec fonction native manipulant la mémoire directement (Car ordre des champs préservés - cf. )

⚠️
SharpLab