Dela via


Poster (F#)

Poster representerar enkla aggregeringar av namngivna värden, eventuellt med medlemmar. De kan antingen vara structs eller referenstyper. De är referenstyper som standard.

Syntax

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

Kommentarer

I den tidigare syntaxen är typename namnet på posttypen, label1 och label2 är namn på värden, som kallas etiketter, och type1 och type2 är typerna av dessa värden. medlemslista är den valfria listan över medlemmar för typen. Du kan använda [<Struct>] attributet för att skapa en struct-post i stället för en post som är en referenstyp.

Här följer några exempel.

// 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 }

När varje etikett finns på en separat rad är semikolonet valfritt.

Du kan ange värden i uttryck som kallas postuttryck. Kompilatorn härleder typen från de etiketter som används (om etiketterna är tillräckligt åtskilda från dem för andra posttyper). Klammerparenteser ({ }) omger postuttrycket. Följande kod visar ett postuttryck som initierar en post med tre flyttalelement med etiketterna x, y och z.

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

Använd inte det förkortade formuläret om det kan finnas en annan typ som också har samma etiketter.

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 }

Etiketterna för den senast deklarerade typen har företräde framför etiketterna för den tidigare deklarerade typen, så i föregående exempel mypoint3D härleds till att vara Point3D. Du kan uttryckligen ange posttypen, som i följande kod.

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

Metoder kan definieras för posttyper precis som för klasstyper.

Skapa poster med hjälp av postuttryck

Du kan initiera poster med hjälp av de etiketter som definieras i posten. Ett uttryck som gör detta kallas ett postuttryck. Använd klammerparenteser för att omsluta postuttrycket och använda semikolonet som avgränsare.

I följande exempel visas hur du skapar en post.

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

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

Semikolonen efter det sista fältet i postuttrycket och i typdefinitionen är valfria, oavsett om alla fält finns på en rad.

När du skapar en post måste du ange värden för varje fält. Du kan inte referera till värdena för andra fält i initieringsuttrycket för något fält.

I följande kod härleds typen av myRecord2 från namnen på fälten. Du kan också ange typnamnet explicit.

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

En annan form av postkonstruktion kan vara användbar när du måste kopiera en befintlig post och eventuellt ändra några av fältvärdena. Följande kodrad illustrerar detta.

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

Den här formen av postuttrycket kallas för uttrycket kopiera och uppdatera post.

Poster är oföränderliga som standard. Du kan dock enkelt skapa ändrade poster med hjälp av ett kopierings- och uppdateringsuttryck. Du kan också uttryckligen ange ett föränderligt fält.

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

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

myCar.Odometer <- myCar.Odometer + 21

Använd inte attributet DefaultValue med postfält. En bättre metod är att definiera standardinstanser av poster med fält som initieras till standardvärden och sedan använda ett kopierings- och uppdateringspostuttryck för att ange fält som skiljer sig från standardvärdena.

// 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 }

Skapa ömsesidigt rekursiva poster

Ibland när du skapar en post kanske du vill att den ska vara beroende av en annan typ som du vill definiera efteråt. Det här är ett kompileringsfel om du inte definierar posttyperna som ömsesidigt rekursiva.

Definitionen av ömsesidigt rekursiva poster görs med nyckelordet and . På så sätt kan du länka ihop 2 eller fler posttyper.

Följande kod definierar till exempel en Person och Address typ som ömsesidigt rekursiv:

// 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 }

Om du vill skapa instanser av båda gör du följande:

// 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
          }
  }

Om du skulle definiera föregående exempel utan nyckelordet and kompileras det inte. Nyckelordet and krävs för ömsesidigt rekursiva definitioner.

Mönstermatchning med poster

Poster kan användas med mönstermatchning. Du kan ange vissa fält explicit och ange variabler för andra fält som ska tilldelas när en matchning inträffar. Följande kodexempel illustrerar detta.

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 }

Utdata för den här koden är följande.

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

Poster och medlemmar

Du kan ange medlemmar på poster ungefär som du kan med klasser. Det finns inget stöd för fält. En vanlig metod är att definiera en Default statisk medlem för enkel postkonstruktion:

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

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

let defaultPerson = Person.Default

Om du använder en självidentifierare refererar identifieraren till instansen av posten vars medlem heter:

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()

Skillnader mellan poster och klasser

Postfält skiljer sig från klassfält eftersom de automatiskt exponeras som egenskaper, och de används vid skapande och kopiering av poster. Rekordkonstruktion skiljer sig också från klasskonstruktion. I en posttyp kan du inte definiera en konstruktor. I stället gäller den byggsyntax som beskrivs i det här avsnittet. Klasser har ingen direkt relation mellan konstruktorparametrar, fält och egenskaper.

Precis som unions- och strukturtyper har poster strukturell likhetssemantik. Klasser har referensjämlikhetssemantik. Följande kodexempel visar detta.

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."

Utdata för den här koden är följande:

The records are equal.

Om du skriver samma kod med klasser skulle de två klassobjekten vara ojämlika eftersom de två värdena skulle representera två objekt på heapen och endast adresserna skulle jämföras (om inte klasstypen åsidosätter System.Object.Equals metoden).

Om du behöver referensjämlikhet för poster lägger du till attributet [<ReferenceEquality>] ovanför posten.

Se även