Klasy (F#)

Klasy to typy reprezentujące obiekty, które mogą mieć właściwości, metody i zdarzenia.

Składnia

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

Uwagi

Klasy reprezentują podstawowy opis typów obiektów platformy .NET; klasa jest podstawową koncepcją typu, która obsługuje programowanie obiektowe w języku F#.

W poprzedniej składni parametr type-name jest dowolnym prawidłowym identyfikatorem. Opis type-params opcjonalnych parametrów typu ogólnego. Składa się z nazw parametrów typu i ograniczeń ujętych w nawiasy kątowe (< i >). Aby uzyskać więcej informacji, zobacz Ogólne i ograniczenia. Opis parameter-list parametrów konstruktora. Pierwszy modyfikator dostępu odnosi się do typu; drugi odnosi się do konstruktora podstawowego. W obu przypadkach wartość domyślna to public.

Należy określić klasę bazową dla klasy przy użyciu słowa kluczowego inherit . Należy podać argumenty w nawiasach dla konstruktora klasy bazowej.

Zadeklarujesz pola lub wartości funkcji, które są lokalne dla klasy przy użyciu let powiązań, i musisz postępować zgodnie z ogólnymi regułami powiązań let . Sekcja do-bindings zawiera kod, który ma zostać wykonany podczas konstruowania obiektów.

Obiekt member-list składa się z dodatkowych konstruktorów, wystąpień i deklaracji metod statycznych, deklaracji interfejsu, powiązań abstrakcyjnych oraz deklaracji właściwości i zdarzeń. Są one opisane w temacie Członkowie.

Element identifier używany z opcjonalnym as słowem kluczowym nadaje nazwę zmiennej wystąpienia lub identyfikator własny, który może służyć w definicji typu do odwoływania się do wystąpienia typu. Aby uzyskać więcej informacji, zobacz sekcję Self Identifiers w dalszej części tego tematu.

Słowa kluczowe class i end oznaczające początek i koniec definicji są opcjonalne.

Wzajemnie rekursywne typy, które odwołują się do siebie, są połączone ze and słowem kluczowym tak samo, jak wzajemnie rekursywne są funkcje. Aby zapoznać się z przykładem, zobacz sekcję Wzajemnie rekursywne Typy.

Konstruktory

Konstruktor to kod, który tworzy wystąpienie typu klasy. Konstruktory klas działają nieco inaczej w języku F#, niż w innych językach platformy .NET. W klasie F# zawsze istnieje podstawowy konstruktor, którego argumenty są opisane w parameter-list pliku , który jest zgodny z nazwą typu, i którego treść składa się z let powiązań (i let rec) na początku deklaracji klasy i do powiązań, które następują. Argumenty konstruktora podstawowego znajdują się w zakresie w całej deklaracji klasy.

Możesz dodać dodatkowe konstruktory, używając słowa kluczowego new , aby dodać element członkowski w następujący sposób:

new(argument-list) = constructor-body

Treść nowego konstruktora musi wywołać podstawowy konstruktor określony w górnej części deklaracji klasy.

Poniższy przykład ilustruje tę koncepcję. W poniższym kodzie MyClass ma dwa konstruktory, podstawowy konstruktor, który przyjmuje dwa argumenty i inny konstruktor, który nie przyjmuje żadnych argumentów.

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

let and do dojścia do powiązań

Powiązania let i do w definicji klasy tworzą treść konstruktora klasy podstawowej i dlatego są uruchamiane za każdym razem, gdy zostanie utworzone wystąpienie klasy. let Jeśli powiązanie jest funkcją, jest ono kompilowane w elemencie członkowskim. let Jeśli powiązanie jest wartością, która nie jest używana w żadnej funkcji lub składowej, jest kompilowana w zmiennej lokalnej dla konstruktora. W przeciwnym razie jest on kompilowany w polu klasy. Poniższe do wyrażenia są kompilowane w konstruktorze podstawowym i wykonują kod inicjowania dla każdego wystąpienia. Ponieważ wszystkie dodatkowe konstruktory zawsze nazywają konstruktor podstawowy, let powiązania i do powiązania zawsze są wykonywane niezależnie od tego, który konstruktor jest wywoływany.

Dostęp do pól tworzonych przez let powiązania można uzyskać w obrębie metod i właściwości klasy, ale nie można uzyskać do nich dostępu z metod statycznych, nawet jeśli metody statyczne przyjmują zmienną wystąpienia jako parametr. Nie można uzyskać do nich dostępu przy użyciu identyfikatora własnego, jeśli istnieje.

Identyfikatory własne

Identyfikator własny to nazwa reprezentująca bieżące wystąpienie. Identyfikatory własne przypominają this słowo kluczowe w języku C# lub C++ lub Me Visual Basic. Możesz zdefiniować identyfikator własny na dwa różne sposoby, w zależności od tego, czy chcesz, aby identyfikator własny był w zakresie dla całej definicji klasy, czy tylko dla pojedynczej metody.

Aby zdefiniować identyfikator własny dla całej klasy, użyj as słowa kluczowego po nawiasach zamykających listy parametrów konstruktora i określ nazwę identyfikatora.

Aby zdefiniować identyfikator własny tylko dla jednej metody, podaj identyfikator własny w deklaracji elementu członkowskiego, tuż przed nazwą metody i kropką (.) jako separatorem.

Poniższy przykład kodu ilustruje dwa sposoby utworzenia własnego identyfikatora. W pierwszym wierszu as słowo kluczowe jest używane do definiowania identyfikatora własnego. W piątym wierszu identyfikator this jest używany do definiowania identyfikatora samodzielnego, którego zakres jest ograniczony do metody PrintMessage.

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

W przeciwieństwie do innych języków platformy .NET można nazwać identyfikatorem własnym, jednak chcesz; Nie masz ograniczeń do nazw, takich jak self, Melub this.

Identyfikator własny zadeklarowany za pomocą słowa kluczowego as nie jest inicjowany dopiero po konstruktorze podstawowym. W związku z tym w przypadku użycia przed konstruktorem podstawowym lub wewnątrz niego System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized. zostanie zgłoszony podczas wykonywania. Możesz swobodnie użyć identyfikatora samodzielnego po konstruktorze podstawowym, takim jak let powiązania lub do powiązania.

Parametry typu ogólnego

Parametry typu ogólnego są określane w nawiasach kątowych (< i >), w postaci pojedynczego cudzysłowu, po którym następuje identyfikator. Wiele parametrów typu ogólnego jest rozdzielonych przecinkami. Parametr typu ogólnego jest w zakresie w całej deklaracji. Poniższy przykład kodu przedstawia sposób określania ogólnych parametrów typu.

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

Argumenty typu są wnioskowane, gdy jest używany typ. W poniższym kodzie typ wnioskowany jest sekwencją krotki.

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

Określanie dziedziczenia

Klauzula inherit identyfikuje bezpośrednią klasę bazową, jeśli istnieje. W języku F# dozwolona jest tylko jedna bezpośrednia klasa bazowa. Interfejsy implementowane przez klasę nie są traktowane jako klasy bazowe. Interfejsy zostały omówione w temacie Interfejsy .

Możesz uzyskać dostęp do metod i właściwości klasy bazowej z klasy pochodnej przy użyciu słowa kluczowego base języka jako identyfikatora, a następnie kropki (.) i nazwy elementu członkowskiego.

Aby uzyskać więcej informacji, zobacz Dziedziczenie.

Sekcja Elementy członkowskie

W tej sekcji można zdefiniować metody statyczne lub metody wystąpienia, właściwości, implementacje interfejsu, abstrakcyjne elementy członkowskie, deklaracje zdarzeń i dodatkowe konstruktory. Nie można wyświetlić powiązań w tej sekcji. Ponieważ elementy członkowskie można dodawać do różnych typów języka F# oprócz klas, są one omawiane w osobnym temacie Członkowie.

Wzajemnie rekursywne typy

Podczas definiowania typów odwołujących się do siebie w sposób cykliczny ciąguje się razem definicje typów przy użyciu słowa kluczowego and . Słowo and kluczowe zastępuje type słowo kluczowe dla wszystkich z wyjątkiem pierwszej definicji w następujący sposób.

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

Dane wyjściowe to lista wszystkich plików w bieżącym katalogu.

Kiedy należy używać klas, unii, rekordów i struktur

Biorąc pod uwagę różne typy do wyboru, musisz mieć dobrą wiedzę na temat tego, co każdy typ jest przeznaczony do wybrania odpowiedniego typu dla określonej sytuacji. Klasy są przeznaczone do użytku w kontekstach programowania obiektowego. Programowanie obiektowe to dominujący paradygmat używany w aplikacjach napisanych dla programu .NET Framework. Jeśli kod języka F# musi ściśle współpracować z platformą .NET Framework lub inną biblioteką zorientowaną na obiekty, a zwłaszcza jeśli trzeba rozszerzyć system typów zorientowanych na obiekt, taki jak biblioteka interfejsu użytkownika, klasy są prawdopodobnie odpowiednie.

Jeśli nie współpracujesz ściśle z kodem obiektowym lub jeśli piszesz kod, który jest samodzielny i dlatego chroniony przed częstą interakcją z kodem obiektowym, należy rozważyć użycie kombinacji klas, rekordów i związków dyskryminowanych. Jedna, dobrze przemyślana unia dyskryminowana, wraz z odpowiednim kodem pasującym do wzorca, często może być używana jako prostsza alternatywa dla hierarchii obiektów. Aby uzyskać więcej informacji na temat związków dyskryminowanych, zobacz Dyskryminowane związki zawodowe.

Rekordy mają przewagę nad prostszą niż klasą, ale rekordy nie są odpowiednie, gdy wymagania typu przekraczają to, co można osiągnąć z ich prostotą. Rekordy są zasadniczo prostymi agregacjami wartości bez oddzielnych konstruktorów, które mogą wykonywać akcje niestandardowe, bez ukrytych pól i bez dziedziczenia lub implementacji interfejsu. Chociaż elementy członkowskie, takie jak właściwości i metody, można dodawać do rekordów, aby ich zachowanie było bardziej złożone, pola przechowywane w rekordzie są nadal prostą agregą wartości. Aby uzyskać więcej informacji na temat rekordów, zobacz Rekordy.

Struktury są również przydatne w przypadku małych agregacji danych, ale różnią się one od klas i rekordów, w których są typami wartości platformy .NET. Klasy i rekordy są typami referencyjnymi platformy .NET. Semantyka typów wartości i typów referencyjnych różni się w przypadku typów wartości przekazywanych przez wartość. Oznacza to, że są one kopiowane bit na bit, gdy są przekazywane jako parametr lub zwracane z funkcji. Są one również przechowywane na stosie lub, jeśli są używane jako pole, osadzone wewnątrz obiektu nadrzędnego zamiast przechowywane we własnej oddzielnej lokalizacji na stercie. W związku z tym struktury są odpowiednie dla często używanych danych, gdy obciążenie związane z uzyskiwaniem dostępu do stert jest problemem. Aby uzyskać więcej informacji na temat struktur, zobacz Structs (Struktury).

Zobacz też