Partager via


Enregistrements (F#)

Les enregistrements représentent des agrégats simples de valeurs nommées, éventuellement avec des membres. Ils peuvent être des structs ou des types référence. Ils sont des types de référence par défaut.

Syntaxe

[ attributes ]
type [accessibility-modifier] typename =
    [accessibility-modifier] { 
        [ mutable ] label1 : type1;
        [ mutable ] label2 : type2;
        ... 
    }
    [ member-list ]

Avant accessibility modifier d’affecter typename la visibilité de l’ensemble du type et est public par défaut. La deuxième accessibility modifier affecte uniquement le constructeur et les champs.

Remarques

Dans la syntaxe précédente, typename est le nom du type d’enregistrement, label1 et label2 sont des noms de valeurs, appelés étiquettes, et type1 et type2 sont les types de ces valeurs. member-list est la liste facultative des membres pour le type. Vous pouvez utiliser l’attribut [<Struct>] pour créer un enregistrement de struct plutôt qu’un enregistrement qui est un type de référence.

Voici quelques exemples.

// Labels are separated by semicolons when defined on the same line.
type Point = { X: float; Y: float; Z: float }

// You can define labels on their own line with or without a semicolon.
type Customer =
    { First: string
      Last: string
      SSN: uint32
      AccountNumber: uint32 }

// A struct record.
[<Struct>]
type StructPoint = { X: float; Y: float; Z: float }

Lorsque chaque étiquette se trouve sur une ligne distincte, le point-virgule est facultatif.

Vous pouvez définir des valeurs dans les expressions appelées expressions d’enregistrement. Le compilateur déduit le type des étiquettes utilisées (si les étiquettes sont suffisamment distinctes de celles d’autres types d’enregistrements). Accolades ({ }) entourent l’expression d’enregistrement. Le code suivant montre une expression d’enregistrement qui initialise un enregistrement avec trois éléments float avec des étiquettes xet yz.

let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }

N’utilisez pas le formulaire raccourci s’il peut y avoir un autre type qui possède également les mêmes étiquettes.

type Point = { X: float; Y: float; Z: float }
type Point3D = { X: float; Y: float; Z: float }
// Ambiguity: Point or Point3D?
let mypoint3D = { X = 1.0; Y = 1.0; Z = 0.0 }

Les étiquettes du type le plus récemment déclaré sont prioritaires sur celles du type précédemment déclaré. Par conséquent, dans l’exemple précédent, mypoint3D elle est déduite d’être Point3D. Vous pouvez spécifier explicitement le type d’enregistrement, comme dans le code suivant.

let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }

Les méthodes peuvent être définies pour les types d’enregistrements comme pour les types de classes.

Création d’enregistrements à l’aide d’expressions d’enregistrement

Vous pouvez initialiser des enregistrements à l’aide des étiquettes définies dans l’enregistrement. Expression qui effectue cette opération est appelée expression d’enregistrement. Utilisez des accolades pour placer l’expression d’enregistrement et utiliser le point-virgule comme délimiteur.

L’exemple suivant montre comment créer un enregistrement.

type MyRecord = { X: int; Y: int; Z: int }

let myRecord1 = { X = 1; Y = 2; Z = 3 }

Les points-virgules après le dernier champ de l’expression d’enregistrement et dans la définition de type sont facultatifs, que les champs soient tous en une seule ligne.

Lorsque vous créez un enregistrement, vous devez fournir des valeurs pour chaque champ. Vous ne pouvez pas faire référence aux valeurs d’autres champs dans l’expression d’initialisation pour n’importe quel champ.

Dans le code suivant, le type de myRecord2 données est déduit des noms des champs. Si vous le souhaitez, vous pouvez spécifier explicitement le nom de type.

let myRecord2 =
    { MyRecord.X = 1
      MyRecord.Y = 2
      MyRecord.Z = 3 }

Une autre forme de construction d’enregistrement peut être utile lorsque vous devez copier un enregistrement existant et éventuellement modifier certaines des valeurs de champ. La ligne de code suivante illustre cela.

let myRecord3 = { myRecord2 with Y = 100; Z = 2 }

Cette forme de l’expression d’enregistrement est appelée expression de copie et de mise à jour de l’enregistrement.

Les enregistrements sont immuables par défaut ; Toutefois, vous pouvez facilement créer des enregistrements modifiés à l’aide d’une copie et d’une expression de mise à jour. Vous pouvez également spécifier explicitement un champ mutable.

type Car =
    { Make: string
      Model: string
      mutable Odometer: int }

let myCar =
    { Make = "Fabrikam"
      Model = "Coupe"
      Odometer = 108112 }

myCar.Odometer <- myCar.Odometer + 21

N’utilisez pas l’attribut DefaultValue avec les champs d’enregistrement. Une meilleure approche consiste à définir les instances par défaut d’enregistrements avec des champs qui sont initialisés en valeurs par défaut, puis à utiliser une expression de copie et de mise à jour d’enregistrement pour définir les champs qui diffèrent des valeurs par défaut.

// Rather than use [<DefaultValue>], define a default record.
type MyRecord =
    { Field1 : int
      Field2 : int }

let defaultRecord1 = { Field1 = 0; Field2 = 0 }
let defaultRecord2 = { Field1 = 1; Field2 = 25 }

// Use the with keyword to populate only a few chosen fields
// and leave the rest with default values.
let rr3 = { defaultRecord1 with Field2 = 42 }

Création d’enregistrements récursifs mutuellement

Parfois, lors de la création d’un enregistrement, vous souhaiterez peut-être qu’il dépend d’un autre type que vous souhaitez définir par la suite. Il s’agit d’une erreur de compilation, sauf si vous définissez les types d’enregistrements pour qu’ils soient récursifs mutuellement.

La définition d’enregistrements récursifs mutuellement est effectuée avec le and mot clé. Cela vous permet de lier 2 types d’enregistrements ou plus ensemble.

Par exemple, le code suivant définit un type et Person un Address type comme récursifs mutuellement :

// Create a Person type and use the Address type that is not defined
type Person =
    { Name: string
      Age: int
      Address: Address }
// Define the Address type which is used in the Person record
and Address =
    { Line1: string
      Line2: string
      PostCode: string
      Occupant: Person }

Pour créer des instances des deux, procédez comme suit :

// Create a Person type and use the Address type that is not defined
let rec person =
    {
        Name = "Person name"
        Age = 12
        Address =
            {
                Line1 = "line 1"
                Line2 = "line 2"
                PostCode = "abc123"
                Occupant = person
            }
    }

Si vous deviez définir l’exemple précédent sans le and mot clé, il ne serait pas compilé. Le and mot clé est requis pour les définitions récursives mutuellement.

Correspondance des modèles avec les enregistrements

Les enregistrements peuvent être utilisés avec des critères correspondants. Vous pouvez spécifier certains champs explicitement et fournir des variables pour d’autres champs qui seront affectés lorsqu’une correspondance se produit. L’exemple de code suivant illustre cela.

type Point3D = { X: float; Y: float; Z: float }
let evaluatePoint (point: Point3D) =
    match point with
    | { X = 0.0; Y = 0.0; Z = 0.0 } -> printfn "Point is at the origin."
    | { X = xVal; Y = 0.0; Z = 0.0 } -> printfn "Point is on the x-axis. Value is %f." xVal
    | { X = 0.0; Y = yVal; Z = 0.0 } -> printfn "Point is on the y-axis. Value is %f." yVal
    | { X = 0.0; Y = 0.0; Z = zVal } -> printfn "Point is on the z-axis. Value is %f." zVal
    | { X = xVal; Y = yVal; Z = zVal } -> printfn "Point is at (%f, %f, %f)." xVal yVal zVal

evaluatePoint { X = 0.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 100.0; Y = 0.0; Z = 0.0 }
evaluatePoint { X = 10.0; Y = 0.0; Z = -1.0 }

La sortie de ce code est la suivante.

Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).

Enregistrements et membres

Vous pouvez spécifier des membres sur des enregistrements comme vous pouvez avec des classes. Il n’existe aucune prise en charge des champs. Une approche courante consiste à définir un Default membre statique pour faciliter la construction des enregistrements :

type Person =
    { Name: string
      Age: int
      Address: string }

    static member Default =
        { Name = "Phillip"
          Age = 12
          Address = "123 happy fun street" }

let defaultPerson = Person.Default

Si vous utilisez un auto-identificateur, cet identificateur fait référence à l’instance de l’enregistrement dont le membre est appelé :

type Person =
    { Name: string
      Age: int
      Address: string }

    member this.WeirdToString() =
        this.Name + this.Address + string this.Age

let p = { Name = "a"; Age = 12; Address = "abc123" }
let weirdString = p.WeirdToString()

Modificateurs d’accessibilité sur les enregistrements

Le code suivant illustre l’utilisation de modificateurs d’accessibilité. Il existe trois fichiers dans le projet : Module1.fs, Test1.fset Test2.fs. Un type d’enregistrement interne et un type d’enregistrement avec un constructeur privé sont définis dans Module1.

// Module1.fs

module Module1

type internal internalRecd = { X: int }

type recdWithInternalCtor = private { Y: int }

Dans le Test1.fs fichier, l’enregistrement interne doit être initialisé avec le internal modificateur d’accès, c’est parce que le niveau de protection de la valeur et l’enregistrement doivent correspondre, et les deux doivent appartenir au même assembly.

// Test1.fs

module Test1

open Module1

let myInternalRecd1 = { X = 2 } // This line will cause a compiler error.

let internal myInternalRecd2 = { X = 4 } // This is OK

Dans le Test2.fs fichier, l’enregistrement avec le constructeur privé ne peut pas être initialisé directement en raison du niveau de protection du constructeur.

// Test2.fs

module Test2

open Module1

let myRecdWithInternalCtor = { Y = 6 } // This line will cause a compiler error.

Pour plus d’informations sur les modificateurs d’accessibilité, consultez l’article Contrôle d’accès .

Différences entre les enregistrements et les classes

Les champs d’enregistrement diffèrent des champs de classe, car ils sont automatiquement exposés en tant que propriétés, et ils sont utilisés dans la création et la copie d’enregistrements. La construction d’enregistrements diffère également de la construction de classe. Dans un type d’enregistrement, vous ne pouvez pas définir de constructeur. Au lieu de cela, la syntaxe de construction décrite dans cette rubrique s’applique. Les classes n’ont aucune relation directe entre les paramètres de constructeur, les champs et les propriétés.

Comme les types d’union et de structure, les enregistrements ont une sémantique d’égalité structurelle. Les classes ont une sémantique d’égalité de référence. L’exemple de code suivant illustre ceci.

type RecordTest = { X: int; Y: int }

let record1 = { X = 1; Y = 2 }
let record2 = { X = 1; Y = 2 }

if (record1 = record2) then
    printfn "The records are equal."
else
    printfn "The records are unequal."

La sortie de ce code est la suivante :

The records are equal.

Si vous écrivez le même code avec des classes, les deux objets de classe seraient inégaux, car les deux valeurs représenteraient deux objets sur le tas et seules les adresses seraient comparées (sauf si le type de classe remplace la System.Object.Equals méthode).

Si vous avez besoin d’une égalité de référence pour les enregistrements, ajoutez l’attribut [<ReferenceEquality>] au-dessus de l’enregistrement.

Voir aussi