Klasser (F#)
Klasser är typer som representerar objekt som kan ha egenskaper, metoder och händelser.
Syntax
// Class definition:
type [access-modifier] type-name [type-params] [access-modifier] ( parameter-list ) [ as identifier ] =
[ class ]
[ inherit base-type-name(base-constructor-args) ]
[ let-bindings ]
[ do-bindings ]
member-list
...
[ end ]
// Mutually recursive class definitions:
type [access-modifier] type-name1 ...
and [access-modifier] type-name2 ...
...
Kommentarer
Klasser representerar den grundläggande beskrivningen av .NET-objekttyper. klassen är det primära typkonceptet som stöder objektorienterad programmering i F#.
I föregående syntax type-name
är en giltig identifierare. Beskriver type-params
valfria generiska typparametrar. Den består av typparameternamn och begränsningar som omges av vinkelparenteser (<
och >
). Mer information finns i Generiska objekt och begränsningar. Beskriver parameter-list
konstruktorparametrar. Den första åtkomstmodifieraren avser typen; den andra gäller den primära konstruktorn. I båda fallen är public
standardvärdet .
Du anger basklassen för en klass med hjälp av nyckelordet inherit
. Du måste ange argument i parenteser för basklasskonstruktorn.
Du deklarerar fält eller funktionsvärden som är lokala för klassen med hjälp let
av bindningar, och du måste följa de allmänna reglerna för let
bindningar. Avsnittet do-bindings
innehåller kod som ska köras vid objektkonstruktion.
Består member-list
av ytterligare konstruktorer, instans- och statiska metoddeklarationer, gränssnittsdeklarationer, abstrakta bindningar samt egenskaps- och händelsedeklarationer. Dessa beskrivs i Ledamöterna.
Det identifier
som används med det valfria as
nyckelordet ger ett namn till instansvariabeln, eller självidentifierare, som kan användas i typdefinitionen för att referera till instansen av typen. Mer information finns i avsnittet Självidentifierare senare i det här avsnittet.
class
Nyckelorden och end
som markerar början och slutet av definitionen är valfria.
Ömsesidigt rekursiva typer, som är typer som refererar till varandra, kopplas ihop med nyckelordet and
precis som ömsesidigt rekursiva funktioner är. Ett exempel finns i avsnittet Ömsesidigt rekursiva typer.
Konstruktorer
Konstruktorn är kod som skapar en instans av klasstypen. Konstruktorer för klasser fungerar något annorlunda i F# än på andra .NET-språk. I en F#-klass finns det alltid en primär konstruktor vars argument beskrivs i parameter-list
som följer typnamnet och vars brödtext består av let
(och let rec
) bindningar i början av klassdeklarationen och bindningarna do
som följer. Argumenten för den primära konstruktorn finns i omfånget i hela klassdeklarationen.
Du kan lägga till ytterligare konstruktorer med hjälp av nyckelordet new
för att lägga till en medlem på följande sätt:
new
(argument-list
) = constructor-body
Den nya konstruktorns brödtext måste anropa den primära konstruktorn som anges högst upp i klassdeklarationen.
I följande exempel visas det här konceptet. I följande kod MyClass
finns två konstruktorer, en primär konstruktor som tar två argument och en annan konstruktor som inte tar några argument.
type MyClass1(x: int, y: int) =
do printfn "%d %d" x y
new() = MyClass1(0, 0)
let and do Bindings
Bindningarna let
och do
i en klassdefinition utgör brödtexten för den primära klasskonstruktorn, och därför körs de när en klassinstans skapas. Om en let
bindning är en funktion kompileras den till en medlem. Om bindningen let
är ett värde som inte används i någon funktion eller medlem kompileras den till en variabel som är lokal för konstruktorn. Annars kompileras den till ett fält i klassen. De do
uttryck som följer kompileras till den primära konstruktorn och kör initieringskoden för varje instans. Eftersom alla ytterligare konstruktorer alltid anropar den primära konstruktorn körs let
bindningarna och do
bindningarna alltid oavsett vilken konstruktor som anropas.
Fält som skapas av let
bindningar kan nås i klassens metoder och egenskaper. De kan dock inte nås från statiska metoder, även om de statiska metoderna använder en instansvariabel som en parameter. De kan inte nås med hjälp av självidentifieraren, om det finns en sådan.
Självidentifierare
En självidentifierare är ett namn som representerar den aktuella instansen. Självidentifierare liknar nyckelordet this
i C# eller C++ eller Me
i Visual Basic. Du kan definiera en självidentifierare på två olika sätt, beroende på om du vill att självidentifieraren ska finnas i omfånget för hela klassdefinitionen eller bara för en enskild metod.
Om du vill definiera en självidentifierare för hela klassen använder du nyckelordet as
efter de avslutande parenteserna i konstruktorparameterlistan och anger identifierarnamnet.
Om du vill definiera en självidentifierare för bara en metod anger du självidentifieraren i medlemsdeklarationen, precis före metodnamnet och en punkt (.) som avgränsare.
I följande kodexempel visas de två sätten att skapa en självidentifierare. På den första raden används nyckelordet as
för att definiera självidentifieraren. På den femte raden används identifieraren this
för att definiera en självidentifierare vars omfång är begränsat till metoden PrintMessage
.
type MyClass2(dataIn) as self =
let data = dataIn
do
self.PrintMessage()
member this.PrintMessage() =
printf "Creating MyClass2 with Data %d" data
Till skillnad från i andra .NET-språk kan du namnge självidentifieraren hur du vill. du är inte begränsad till namn som self
, Me
eller this
.
Självidentifieraren som deklareras med nyckelordet as
initieras inte förrän efter baskonstruktorn. När det används före eller inuti baskonstruktorn System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
utlöses därför under körningen. Du kan använda självidentifieraren fritt efter baskonstruktorn, till exempel i let
bindningar eller do
bindningar.
Allmänna typparametrar
Generiska typparametrar anges i vinkelparenteser (<
och >
), i form av ett enkelt citattecken följt av en identifierare. Flera generiska typparametrar avgränsas med kommatecken. Den generiska typparametern finns i omfånget i hela deklarationen. I följande kodexempel visas hur du anger parametrar av allmän typ.
type MyGenericClass<'a>(x: 'a) =
do printfn "%A" x
Typargument härleds när typen används. I följande kod är den härledda typen en sekvens med tupplar.
let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })
Ange arv
Satsen inherit
identifierar den direkta basklassen, om det finns en. I F# tillåts endast en direkt basklass. Gränssnitt som en klass implementerar betraktas inte som basklasser. Gränssnitt beskrivs i avsnittet Gränssnitt .
Du kan komma åt metoderna och egenskaperna för basklassen från den härledda klassen med hjälp av språknyckelordet base
som identifierare, följt av en punkt (.) och namnet på medlemmen.
Mer information finns i Arv.
Avsnittet Medlemmar
Du kan definiera statiska metoder eller instansmetoder, egenskaper, gränssnittsimplementeringar, abstrakta medlemmar, händelsedeklarationer och ytterligare konstruktorer i det här avsnittet. Det går inte att visa bindningar i det här avsnittet. Eftersom medlemmar kan läggas till i en mängd olika F#-typer utöver klasser, diskuteras de i ett separat ämne, Medlemmar.
Ömsesidigt rekursiva typer
När du definierar typer som refererar till varandra på ett cirkulärt sätt, stränger du samman typdefinitionerna med hjälp av nyckelordet and
. Nyckelordet and
ersätter nyckelordet type
för alla utom den första definitionen enligt följande.
open System.IO
type Folder(pathIn: string) =
let path = pathIn
let filenameArray: string array = Directory.GetFiles(path)
member this.FileArray = Array.map (fun elem -> new File(elem, this)) filenameArray
and File(filename: string, containingFolder: Folder) =
member this.Name = filename
member this.ContainingFolder = containingFolder
let folder1 = new Folder(".")
for file in folder1.FileArray do
printfn "%s" file.Name
Utdata är en lista över alla filer i den aktuella katalogen.
När du ska använda klasser, fackföreningar, poster och strukturer
Med tanke på de olika typerna att välja mellan måste du ha en god förståelse för vad varje typ är utformad för för att välja lämplig typ för en viss situation. Klasser är utformade för användning i objektorienterade programmeringskontexter. Objektorienterad programmering är det dominerande paradigmet som används i program som är skrivna för .NET Framework. Om F#-koden måste ha ett nära samarbete med .NET Framework eller ett annat objektorienterat bibliotek, och särskilt om du måste utöka från ett objektorienterat typsystem, till exempel ett gränssnittsbibliotek, är klasser förmodligen lämpliga.
Om du inte samverkar nära med objektorienterad kod, eller om du skriver kod som är fristående och därför skyddas från frekvent interaktion med objektorienterad kod, bör du överväga att använda en blandning av klasser, poster och diskriminerade fackföreningar. En enda, väl genomtänkt diskrimineringsunion, tillsammans med lämplig mönstermatchningskod, kan ofta användas som ett enklare alternativ till en objekthierarki. Mer information om diskriminerade fackföreningar finns i Diskriminerade fackföreningar.
Poster har fördelen att vara enklare än klasser, men poster är inte lämpliga när kraven av en typ överskrider vad som kan åstadkommas med deras enkelhet. Poster är i princip enkla aggregeringar av värden, utan separata konstruktorer som kan utföra anpassade åtgärder, utan dolda fält och utan arvs- eller gränssnittsimplementeringar. Även om medlemmar som egenskaper och metoder kan läggas till i poster för att göra deras beteende mer komplext, är fälten som lagras i en post fortfarande en enkel mängd värden. Mer information om poster finns i Poster.
Strukturer är också användbara för små mängder data, men de skiljer sig från klasser och poster eftersom de är .NET-värdetyper. Klasser och poster är .NET-referenstyper. Semantiken för värdetyper och referenstyper skiljer sig i att värdetyper skickas efter värde. Det innebär att de kopieras bit för bit när de skickas som en parameter eller returneras från en funktion. De lagras också i stacken eller, om de används som ett fält, inbäddade i det överordnade objektet i stället för att lagras på en egen separat plats i heapen. Därför är strukturer lämpliga för data som används ofta när kostnaderna för att komma åt heapen är ett problem. Mer information om strukturer finns i Structs.