Delen via


Klassen (F#)

Klassen zijn typen die objecten vertegenwoordigen die eigenschappen, methoden en gebeurtenissen kunnen hebben.

Syntaxis

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

Opmerkingen

Klassen vertegenwoordigen de fundamentele beschrijving van .NET-objecttypen; de klasse is het primaire typeconcept dat objectgeoriënteerde programmering in F# ondersteunt.

In de voorgaande syntaxis is dit type-name een geldige id. Hierin type-params worden optionele algemene typeparameters beschreven. Het bestaat uit type parameternamen en beperkingen tussen punthaken (< en >). Zie Generics en Beperkingen voor meer informatie. De parameter-list parameters van de constructor worden beschreven. De eerste wijzigingsfunctie voor toegang heeft betrekking op het type; de tweede heeft betrekking op de primaire constructor. In beide gevallen is de standaardwaarde public.

U geeft de basisklasse voor een klasse op met behulp van het inherit trefwoord. U moet argumenten opgeven tussen haakjes voor de basisklasseconstructor.

U declareert velden of functiewaarden die lokaal zijn voor de klasse met behulp van let bindingen en u moet de algemene regels voor let bindingen volgen. De do-bindings sectie bevat code die moet worden uitgevoerd bij het bouwen van objecten.

Het member-list bestaat uit extra constructors, instantie- en statische methodedeclaraties, interfacedeclaraties, abstracte bindingen en eigenschaps- en gebeurtenisdeclaraties. Deze worden beschreven in leden.

De identifier naam die wordt gebruikt met het optionele as trefwoord geeft een naam aan de instantievariabele of zelf-id, die kan worden gebruikt in de typedefinitie om te verwijzen naar het exemplaar van het type. Zie de sectie Zelf-id's verderop in dit onderwerp voor meer informatie.

De trefwoorden class en end die het begin en einde van de definitie markeren, zijn optioneel.

Wederzijds recursieve typen, die naar elkaar verwijzen, worden samengevoegd met het and trefwoord, net zoals wederzijds recursieve functies zijn. Zie de sectie Wederzijds recursieve typen voor een voorbeeld.

Constructors

De constructor is code waarmee een exemplaar van het klassetype wordt gemaakt. Constructors voor klassen werken enigszins anders in F# dan in andere .NET-talen. In een F#-klasse is er altijd een primaire constructor waarvan de argumenten worden beschreven in de naam van het parameter-list type en waarvan de hoofdtekst bestaat uit de let (en let rec) bindingen aan het begin van de klassedeclaratie en de do bindingen die volgen. De argumenten van de primaire constructor vallen binnen het bereik van de klassedeclaratie.

U kunt als volgt extra constructors toevoegen met behulp van het new trefwoord om een lid toe te voegen:

new(argument-list) = constructor-body

De hoofdtekst van de nieuwe constructor moet de primaire constructor aanroepen die boven aan de klassedeclaratie is opgegeven.

In het volgende voorbeeld ziet u dit concept. In de volgende code MyClass hebben twee constructors, een primaire constructor die twee argumenten gebruikt en een andere constructor die geen argumenten gebruikt.

type MyClass1(x: int, y: int) =
    do printfn "%d %d" x y
    new() = MyClass1(0, 0)

bindingen laten en uitvoeren

De let bindingen do in een klassedefinitie vormen de hoofdtekst van de primaire klasseconstructor en worden daarom uitgevoerd wanneer een klasse-exemplaar wordt gemaakt. Als een let binding een functie is, wordt deze gecompileerd in een lid. Als de let binding een waarde is die niet wordt gebruikt in een functie of lid, wordt deze gecompileerd in een variabele die lokaal is voor de constructor. Anders wordt deze gecompileerd in een veld van de klasse. De do volgende expressies worden gecompileerd in de primaire constructor en voeren initialisatiecode uit voor elk exemplaar. Omdat eventuele extra constructors altijd de primaire constructor aanroepen, worden de let bindingen en do bindingen altijd uitgevoerd, ongeacht welke constructor wordt aangeroepen.

Velden die door let bindingen worden gemaakt, kunnen worden geopend via de methoden en eigenschappen van de klasse. Ze kunnen echter niet worden geopend vanuit statische methoden, zelfs als de statische methoden een exemplaarvariabele als parameter gebruiken. Ze kunnen niet worden geopend met behulp van de self-id, als deze bestaat.

Zelf-id's

Een self-id is een naam die het huidige exemplaar vertegenwoordigt. Self-id's lijken op het this trefwoord in C# of C++ of Me in Visual Basic. U kunt een self-id op twee verschillende manieren definiëren, afhankelijk van of u wilt dat de zelf-id binnen het bereik van de hele klassedefinitie of alleen voor een afzonderlijke methode valt.

Als u een self-id voor de hele klasse wilt definiëren, gebruikt u het as trefwoord na de haakjes sluiten van de lijst met constructorparameters en geeft u de id-naam op.

Als u een self-id voor slechts één methode wilt definiëren, geeft u de zelf-id op in de liddeclaratie, net vóór de naam van de methode en een punt (.) als scheidingsteken.

In het volgende codevoorbeeld ziet u de twee manieren om een self-id te maken. In de eerste regel wordt het as trefwoord gebruikt om de zelf-id te definiëren. In de vijfde regel wordt de id this gebruikt om een zelf-id te definiëren waarvan het bereik is beperkt tot de methode PrintMessage.

type MyClass2(dataIn) as self =
    let data = dataIn
    do
        self.PrintMessage()
    member this.PrintMessage() =
        printf "Creating MyClass2 with Data %d" data

In tegenstelling tot andere .NET-talen kunt u de zelf-id de gewenste naam geven; u bent niet beperkt tot namen zoals self, Meof this.

De zelf-id die met het as trefwoord wordt gedeclareerd, wordt pas geïnitialiseerd nadat de basisconstructor is uitgevoerd. Daarom wordt, wanneer deze vóór of binnen de basisconstructor wordt gebruikt, System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. tijdens runtime gegenereerd. U kunt de self-id vrij gebruiken na de basisconstructor, zoals in let bindingen of do bindingen.

Algemene typeparameters

Algemene typeparameters worden opgegeven tussen punthaken (< en >), in de vorm van één aanhalingsteken gevolgd door een id. Meerdere algemene typeparameters worden gescheiden door komma's. De algemene typeparameter valt binnen het bereik van de declaratie. In het volgende codevoorbeeld ziet u hoe u algemene typeparameters opgeeft.

type MyGenericClass<'a>(x: 'a) =
    do printfn "%A" x

Typeargumenten worden afgeleid wanneer het type wordt gebruikt. In de volgende code is het afgeleid type een reeks tuples.

let g1 = MyGenericClass(seq { for i in 1..10 -> (i, i * i) })

Overname opgeven

De inherit component identificeert de directe basisklasse, als er een is. In F# is slechts één directe basisklasse toegestaan. Interfaces die door een klasse worden geïmplementeerd, worden niet beschouwd als basisklassen. Interfaces worden besproken in het onderwerp Interfaces .

U kunt toegang krijgen tot de methoden en eigenschappen van de basisklasse van de afgeleide klasse door het trefwoord base taal als id te gebruiken, gevolgd door een punt (.) en de naam van het lid.

Zie Overname voor meer informatie.

Sectie Leden

U kunt statische methoden of instantiemethoden, eigenschappen, interface-implementaties, abstracte leden, gebeurtenisdeclaraties en aanvullende constructors in deze sectie definiëren. Bindingen mogen niet worden weergegeven in deze sectie. Omdat leden kunnen worden toegevoegd aan verschillende F#-typen naast klassen, worden ze besproken in een afzonderlijk onderwerp, leden.

Wederzijds recursieve typen

Wanneer u typen definieert die op een kringvormige manier naar elkaar verwijzen, tekent u de typedefinities samen met behulp van het and trefwoord. Het and trefwoord vervangt het type trefwoord op alle, behalve de eerste definitie, als volgt.

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

De uitvoer is een lijst met alle bestanden in de huidige map.

Wanneer moet u klassen, samenvoegingen, records en structuren gebruiken

Gezien de verschillende typen waaruit u kunt kiezen, moet u een goed begrip hebben van wat elk type is ontworpen om het juiste type voor een bepaalde situatie te selecteren. Klassen zijn ontworpen voor gebruik in objectgeoriënteerde programmeercontexten. Objectgeoriënteerd programmeren is het dominante paradigma dat wordt gebruikt in toepassingen die zijn geschreven voor .NET Framework. Als uw F#-code nauw moet samenwerken met .NET Framework of een andere objectgeoriënteerde bibliotheek, en vooral als u moet uitbreiden van een objectgeoriënteerd typesysteem zoals een UI-bibliotheek, zijn klassen waarschijnlijk geschikt.

Als u niet nauw samenwerkt met objectgeoriënteerde code of als u code schrijft die onafhankelijk is en daarom wordt beschermd tegen frequente interactie met objectgeoriënteerde code, kunt u overwegen een combinatie van klassen, records en gediscrimineerde samenvoegingen te gebruiken. Een enkele, goed doordachte gediscrimineerde samenvoeging, samen met de juiste patroonkoppelingscode, kan vaak worden gebruikt als een eenvoudiger alternatief voor een objecthiërarchie. Zie Gediscrimineerde vakbonden voor meer informatie over gediscrimineerde vakbonden.

Records hebben het voordeel dat ze eenvoudiger zijn dan klassen, maar records zijn niet geschikt wanneer de eisen van een type groter zijn dan wat kan worden bereikt met hun eenvoud. Records zijn in feite eenvoudige aggregaties van waarden, zonder afzonderlijke constructors die aangepaste acties kunnen uitvoeren, zonder verborgen velden en zonder overname of interface-implementaties. Hoewel leden zoals eigenschappen en methoden kunnen worden toegevoegd aan records om hun gedrag complexer te maken, zijn de velden die zijn opgeslagen in een record nog steeds een eenvoudige aggregaties van waarden. Zie Records voor meer informatie over records.

Structuren zijn ook handig voor kleine aggregaties van gegevens, maar ze verschillen van klassen en records in dat ze .NET-waardetypen zijn. Klassen en records zijn .NET-referentietypen. De semantiek van waardetypen en verwijzingstypen verschillen in die waardetypen worden doorgegeven door de waarde. Dit betekent dat ze bit voor bit worden gekopieerd wanneer ze worden doorgegeven als een parameter of worden geretourneerd vanuit een functie. Ze worden ook opgeslagen op de stapel of, als ze worden gebruikt als veld, ingesloten in het bovenliggende object in plaats van op hun eigen afzonderlijke locatie op de heap. Daarom zijn structuren geschikt voor veelgebruikte gegevens wanneer de overhead van toegang tot de heap een probleem is. Zie Structs voor meer informatie over structuren.

Zie ook