Records (F#)
Records vertegenwoordigen eenvoudige aggregaties van benoemde waarden, optioneel met leden. Ze kunnen structs of verwijzingstypen zijn. Ze zijn standaard referentietypen.
Syntaxis
[ attributes ]
type [accessibility-modifier] typename =
{ [ mutable ] label1 : type1;
[ mutable ] label2 : type2;
... }
[ member-list ]
Opmerkingen
In de vorige syntaxis is typename de naam van het recordtype, label1 en label2 de namen van waarden, aangeduid als labels en type1 en type2 zijn de typen van deze waarden. ledenlijst is de optionele lijst met leden voor het type. U kunt het [<Struct>]
kenmerk gebruiken om een struct-record te maken in plaats van een record die een verwijzingstype is.
Hier volgen enkele voorbeelden.
// 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 }
Wanneer elk label zich op een afzonderlijke regel bevindt, is de puntkomma optioneel.
U kunt waarden instellen in expressies die recordexpressies worden genoemd. De compiler bepaalt het type van de gebruikte labels (als de labels voldoende verschillen van die van andere recordtypen). Accolades ({ }) zetten de recordexpressie in. De volgende code toont een recordexpressie waarmee een record wordt geïnitialiseerd met drie float-elementen met labels x
y
en z
.
let mypoint = { X = 1.0; Y = 1.0; Z = -1.0 }
Gebruik het verkorte formulier niet als er een ander type met dezelfde labels kan zijn.
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 }
De labels van het laatst gedeclareerde type hebben voorrang op die van het eerder gedeclareerde type, dus in het voorgaande voorbeeld mypoint3D
wordt afgeleid.Point3D
U kunt het recordtype expliciet opgeven, zoals in de volgende code.
let myPoint1 = { Point.X = 1.0; Y = 1.0; Z = 0.0 }
Methoden kunnen worden gedefinieerd voor recordtypen, net als voor klassetypen.
Records maken met recordexpressies
U kunt records initialiseren met behulp van de labels die in de record zijn gedefinieerd. Een expressie die dit doet, wordt een recordexpressie genoemd. Gebruik accolades om de recordexpressie in te sluiten en gebruik de puntkomma als scheidingsteken.
In het volgende voorbeeld ziet u hoe u een record maakt.
type MyRecord = { X: int; Y: int; Z: int }
let myRecord1 = { X = 1; Y = 2; Z = 3 }
De puntkomma's na het laatste veld in de recordexpressie en in de typedefinitie zijn optioneel, ongeacht of de velden allemaal in één regel staan.
Wanneer u een record maakt, moet u waarden opgeven voor elk veld. U kunt niet verwijzen naar de waarden van andere velden in de initialisatie-expressie voor een veld.
In de volgende code wordt het type myRecord2
afgeleid van de namen van de velden. Desgewenst kunt u de typenaam expliciet opgeven.
let myRecord2 =
{ MyRecord.X = 1
MyRecord.Y = 2
MyRecord.Z = 3 }
Een andere vorm van recordconstructie kan handig zijn wanneer u een bestaande record moet kopiëren en mogelijk enkele veldwaarden moet wijzigen. De volgende coderegel illustreert dit.
let myRecord3 = { myRecord2 with Y = 100; Z = 2 }
Deze vorm van de recordexpressie wordt de expressie voor het kopiëren en bijwerken van de record genoemd.
Records zijn standaard onveranderbaar; U kunt echter eenvoudig gewijzigde records maken met behulp van een kopieer- en update-expressie. U kunt ook expliciet een veranderlijk veld opgeven.
type Car =
{ Make: string
Model: string
mutable Odometer: int }
let myCar =
{ Make = "Fabrikam"
Model = "Coupe"
Odometer = 108112 }
myCar.Odometer <- myCar.Odometer + 21
Gebruik het kenmerk DefaultValue niet met recordvelden. Een betere benadering is het definiëren van standaardexemplaren van records met velden die zijn geïnitialiseerd op standaardwaarden en vervolgens een kopieer- en updaterecordexpressie gebruiken om velden in te stellen die verschillen van de standaardwaarden.
// 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 }
Wederzijds recursieve records maken
Soms wilt u bij het maken van een record mogelijk afhankelijk zijn van een ander type dat u later wilt definiëren. Dit is een compileerfout, tenzij u de recordtypen definieert die wederzijds recursief zijn.
Het definiëren van wederzijds recursieve records wordt uitgevoerd met het and
trefwoord. Hiermee kunt u twee of meer recordtypen aan elkaar koppelen.
Met de volgende code wordt bijvoorbeeld een Person
en Address
type gedefinieerd als wederzijds recursief:
// 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 }
Als u exemplaren van beide wilt maken, gaat u als volgt te werk:
// 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
}
}
Als u het vorige voorbeeld zonder het and
trefwoord zou definiëren, zou het niet worden gecompileerd. Het and
trefwoord is vereist voor recursieve definities.
Patroonkoppeling met records
Records kunnen worden gebruikt met patroonkoppeling. U kunt bepaalde velden expliciet opgeven en variabelen opgeven voor andere velden die worden toegewezen wanneer er een overeenkomst plaatsvindt. In het volgende codevoorbeeld ziet u dit.
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 }
De uitvoer van deze code is als volgt.
Point is at the origin.
Point is on the x-axis. Value is 100.000000.
Point is at (10.000000, 0.000000, -1.000000).
Records en leden
U kunt leden van records opgeven zoals u dat kunt met klassen. Er is geen ondersteuning voor velden. Een veelvoorkomende aanpak is het definiëren van een Default
statisch lid voor eenvoudige recordconstructie:
type Person =
{ Name: string
Age: int
Address: string }
static member Default =
{ Name = "Phillip"
Age = 12
Address = "123 happy fun street" }
let defaultPerson = Person.Default
Als u een self-id gebruikt, verwijst die id naar het exemplaar van de record waarvan het lid wordt aangeroepen:
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()
Verschillen tussen records en klassen
Recordvelden verschillen van klassevelden omdat ze automatisch worden weergegeven als eigenschappen en worden gebruikt bij het maken en kopiëren van records. Recordconstructie verschilt ook van klasseconstructie. In een recordtype kunt u geen constructor definiëren. In plaats daarvan is de syntaxis van de constructie die in dit onderwerp wordt beschreven, van toepassing. Klassen hebben geen directe relatie tussen constructorparameters, velden en eigenschappen.
Net als samenvoegings- en structuurtypen hebben records structurele semantiek voor gelijkheid. Klassen hebben verwijzingssemantiek voor gelijkheid. In het volgende codevoorbeeld ziet u dit.
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."
De uitvoer van deze code is als volgt:
The records are equal.
Als u dezelfde code schrijft met klassen, zijn de twee klasseobjecten ongelijk omdat de twee waarden twee objecten op de heap vertegenwoordigen en alleen de adressen worden vergeleken (tenzij het klassetype de System.Object.Equals
methode overschrijft).
Als u referentie-gelijkheid voor records nodig hebt, voegt u het kenmerk [<ReferenceEquality>]
boven de record toe.